@absolutejs/voice 0.0.22-beta.57 → 0.0.22-beta.571
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/LICENSE +88 -0
- package/README.md +4543 -1181
- package/dist/angular/index.d.ts +36 -5
- package/dist/angular/index.js +4220 -391
- 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 +4 -1
- package/dist/angular/voice-cost-dashboard.service.d.ts +15 -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.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 +5 -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/client/actions.d.ts +128 -2
- 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 +21 -0
- package/dist/client/callDebuggerWidget.d.ts +30 -0
- package/dist/client/callPlayer.d.ts +41 -0
- package/dist/client/campaignDialerProof.d.ts +25 -0
- package/dist/client/connection.d.ts +4 -3
- package/dist/client/controller.d.ts +1 -1
- package/dist/client/conversationAnalytics.d.ts +30 -0
- package/dist/client/costDashboard.d.ts +27 -0
- package/dist/client/createVoiceStream.d.ts +1 -1
- package/dist/client/deliveryRuntime.d.ts +36 -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 +24 -0
- package/dist/client/htmxBootstrap.js +1068 -73
- package/dist/client/htmxDashboardRenderers.d.ts +71 -0
- package/dist/client/index.d.ts +106 -15
- package/dist/client/index.js +10548 -268
- 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 +56 -0
- package/dist/client/opsActionCenterWidget.d.ts +29 -0
- package/dist/client/opsActionHistory.d.ts +21 -0
- package/dist/client/opsActionHistoryWidget.d.ts +11 -0
- package/dist/client/opsStatus.d.ts +21 -0
- package/dist/client/opsStatusWidget.d.ts +10 -10
- package/dist/client/platformCoverage.d.ts +21 -0
- package/dist/client/platformCoverageWidget.d.ts +38 -0
- package/dist/client/profileComparison.d.ts +21 -0
- package/dist/client/profileComparisonWidget.d.ts +41 -0
- package/dist/client/profileSwitchRecommendation.d.ts +21 -0
- package/dist/client/profileSwitchRecommendationWidget.d.ts +12 -0
- package/dist/client/proofTrends.d.ts +21 -0
- package/dist/client/proofTrendsWidget.d.ts +38 -0
- package/dist/client/providerCapabilities.d.ts +21 -0
- package/dist/client/providerCapabilitiesWidget.d.ts +32 -0
- package/dist/client/providerContracts.d.ts +21 -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 +5 -3
- package/dist/client/providerStatusWidget.d.ts +32 -0
- package/dist/client/reactiveSource.d.ts +8 -0
- package/dist/client/readinessFailures.d.ts +21 -0
- package/dist/client/readinessFailuresWidget.d.ts +42 -0
- package/dist/client/reconnectProfileEvidence.d.ts +21 -0
- package/dist/client/reconnectProfileEvidenceWidget.d.ts +39 -0
- package/dist/client/replayTimeline.d.ts +26 -0
- package/dist/client/routingStatus.d.ts +21 -0
- package/dist/client/routingStatusWidget.d.ts +32 -0
- package/dist/client/sessionObservability.d.ts +21 -0
- package/dist/client/sessionObservabilityWidget.d.ts +31 -0
- package/dist/client/sessionSnapshot.d.ts +23 -0
- package/dist/client/sessionSnapshotWidget.d.ts +33 -0
- package/dist/client/store.d.ts +1 -1
- package/dist/client/traceTimeline.d.ts +21 -0
- package/dist/client/traceTimelineWidget.d.ts +36 -0
- package/dist/client/turnLatency.d.ts +24 -0
- package/dist/client/turnLatencyWidget.d.ts +33 -0
- package/dist/client/turnQuality.d.ts +21 -0
- package/dist/client/turnQualityWidget.d.ts +32 -0
- package/dist/client/voiceWidgetView.d.ts +48 -0
- package/dist/client/workflowStatus.d.ts +5 -3
- package/dist/{agent.d.ts → core/agent.d.ts} +83 -6
- package/dist/core/agentPerformanceReport.d.ts +40 -0
- package/dist/core/agentSquadContract.d.ts +98 -0
- package/dist/core/agentState.d.ts +12 -0
- package/dist/core/agentTools.d.ts +132 -0
- package/dist/core/aiScorecard.d.ts +32 -0
- package/dist/core/aiVoiceModel.d.ts +15 -0
- package/dist/core/amdDetector.d.ts +25 -0
- package/dist/{assistant.d.ts → core/assistant.d.ts} +14 -14
- package/dist/core/assistantExperiment.d.ts +42 -0
- package/dist/{assistantHealth.d.ts → core/assistantHealth.d.ts} +9 -9
- package/dist/{assistantMemory.d.ts → core/assistantMemory.d.ts} +10 -10
- package/dist/core/assistantMode.d.ts +22 -0
- package/dist/{audioConditioning.d.ts → core/audioConditioning.d.ts} +2 -2
- package/dist/core/audit.d.ts +131 -0
- package/dist/core/auditDeliveryRoutes.d.ts +85 -0
- package/dist/core/auditExport.d.ts +34 -0
- package/dist/core/auditRoutes.d.ts +66 -0
- package/dist/core/auditSinks.d.ts +151 -0
- package/dist/core/backchannel.d.ts +18 -0
- package/dist/core/bargeInRoutes.d.ts +56 -0
- package/dist/core/bookingFlow.d.ts +43 -0
- package/dist/core/browserCallProfiles.d.ts +120 -0
- package/dist/core/browserMediaRoutes.d.ts +62 -0
- package/dist/core/cachedTTS.d.ts +30 -0
- package/dist/core/calendarAdapter.d.ts +47 -0
- package/dist/core/calendarSlots.d.ts +35 -0
- package/dist/core/callDebugger.d.ts +66 -0
- package/dist/core/callDisposition.d.ts +38 -0
- package/dist/core/callQuota.d.ts +54 -0
- package/dist/core/callScorecard.d.ts +53 -0
- package/dist/core/callerCRMLinker.d.ts +29 -0
- package/dist/core/callerMemory.d.ts +37 -0
- package/dist/core/callingWindow.d.ts +26 -0
- package/dist/core/campaign.d.ts +795 -0
- package/dist/core/campaignControls.d.ts +37 -0
- package/dist/core/campaignDialers.d.ts +111 -0
- package/dist/core/campaignTemplate.d.ts +16 -0
- package/dist/core/competitiveCoverage.d.ts +141 -0
- package/dist/core/conversationSimulator.d.ts +73 -0
- package/dist/{correction.d.ts → core/correction.d.ts} +5 -4
- package/dist/core/costAccounting.d.ts +76 -0
- package/dist/core/costPredictor.d.ts +74 -0
- package/dist/core/crmCallLogger.d.ts +37 -0
- package/dist/core/crmContract.d.ts +70 -0
- package/dist/core/dataControl.d.ts +180 -0
- package/dist/core/defineVoiceAssistant.d.ts +68 -0
- package/dist/core/deliveryRuntime.d.ts +159 -0
- package/dist/core/deliverySinkRoutes.d.ts +117 -0
- package/dist/core/demoReadyRoutes.d.ts +98 -0
- package/dist/{diagnosticsRoutes.d.ts → core/diagnosticsRoutes.d.ts} +2 -2
- package/dist/core/dncRegistry.d.ts +38 -0
- package/dist/core/dtmfCollector.d.ts +37 -0
- package/dist/{evalRoutes.d.ts → core/evalRoutes.d.ts} +26 -20
- package/dist/{fileStore.d.ts → core/fileStore.d.ts} +34 -20
- package/dist/core/guardrails.d.ts +128 -0
- package/dist/{handoff.d.ts → core/handoff.d.ts} +10 -10
- package/dist/{handoffHealth.d.ts → core/handoffHealth.d.ts} +9 -9
- package/dist/core/holdAudio.d.ts +23 -0
- package/dist/{htmx.d.ts → core/htmx.d.ts} +2 -2
- package/dist/core/htmxDashboardRoutes.d.ts +250 -0
- package/dist/core/iceServers.d.ts +34 -0
- package/dist/core/incidentBundle.d.ts +119 -0
- package/dist/core/incidentTimeline.d.ts +260 -0
- package/dist/core/ivrPlan.d.ts +40 -0
- package/dist/core/latencySlo.d.ts +56 -0
- package/dist/core/liveCoach.d.ts +43 -0
- package/dist/core/liveLatency.d.ts +78 -0
- package/dist/core/liveOps.d.ts +190 -0
- package/dist/core/llmJudge.d.ts +45 -0
- package/dist/{logger.d.ts → core/logger.d.ts} +1 -2
- package/dist/core/mcpToolset.d.ts +58 -0
- package/dist/core/mediaPipelineRoutes.d.ts +171 -0
- package/dist/core/mediaPipelineSurfaces.d.ts +48 -0
- package/dist/{memoryStore.d.ts → core/memoryStore.d.ts} +1 -1
- package/dist/core/midCallSummary.d.ts +27 -0
- package/dist/{modelAdapters.d.ts → core/modelAdapters.d.ts} +52 -7
- package/dist/core/monitor.d.ts +148 -0
- package/dist/core/multilingualProof.d.ts +77 -0
- package/dist/core/noShowPredictor.d.ts +46 -0
- package/dist/core/oauth2TokenSource.d.ts +21 -0
- package/dist/core/observabilityExport.d.ts +501 -0
- package/dist/core/openaiTTS.d.ts +18 -0
- package/dist/core/operationalStatus.d.ts +87 -0
- package/dist/core/operationsRecord.d.ts +371 -0
- package/dist/{ops.d.ts → core/ops.d.ts} +70 -70
- package/dist/core/opsActionAuditRoutes.d.ts +99 -0
- package/dist/{opsConsoleRoutes.d.ts → core/opsConsoleRoutes.d.ts} +11 -8
- package/dist/{opsPresets.d.ts → core/opsPresets.d.ts} +2 -2
- package/dist/core/opsRecovery.d.ts +137 -0
- package/dist/{opsRuntime.d.ts → core/opsRuntime.d.ts} +6 -6
- package/dist/{opsSinks.d.ts → core/opsSinks.d.ts} +19 -19
- package/dist/core/opsStatus.d.ts +76 -0
- package/dist/core/opsStatusRoutes.d.ts +33 -0
- package/dist/{opsWebhook.d.ts → core/opsWebhook.d.ts} +15 -15
- package/dist/core/otelExporter.d.ts +83 -0
- package/dist/core/outcomeContract.d.ts +146 -0
- package/dist/{outcomeRecipes.d.ts → core/outcomeRecipes.d.ts} +4 -4
- package/dist/core/pathway.d.ts +94 -0
- package/dist/core/pathwayCompiler.d.ts +31 -0
- package/dist/core/pathwayGenerator.d.ts +27 -0
- package/dist/core/pathwayRuntime.d.ts +57 -0
- package/dist/core/pathwaySlotCollector.d.ts +29 -0
- package/dist/core/pathwayVisualizer.d.ts +8 -0
- package/dist/core/phoneAgent.d.ts +139 -0
- package/dist/core/phoneAgentProductionSmoke.d.ts +115 -0
- package/dist/core/phoneProvisioning.d.ts +29 -0
- package/dist/core/platformCoverage.d.ts +91 -0
- package/dist/{plugin.d.ts → core/plugin.d.ts} +2 -2
- package/dist/core/postCallAnalysis.d.ts +98 -0
- package/dist/core/postCallSurvey.d.ts +41 -0
- package/dist/{postgresStore.d.ts → core/postgresStore.d.ts} +20 -9
- package/dist/{presets.d.ts → core/presets.d.ts} +3 -3
- package/dist/core/productionReadiness.d.ts +757 -0
- package/dist/core/profileSwitchRecommendation.d.ts +350 -0
- package/dist/core/promptInjectionGuard.d.ts +30 -0
- package/dist/core/proofAssertions.d.ts +32 -0
- package/dist/core/proofPack.d.ts +211 -0
- package/dist/core/proofRunner.d.ts +79 -0
- package/dist/core/proofTrends.d.ts +966 -0
- package/dist/{providerAdapters.d.ts → core/providerAdapters.d.ts} +5 -5
- package/dist/core/providerCapabilities.d.ts +92 -0
- package/dist/core/providerDecisionTraces.d.ts +130 -0
- package/dist/{providerHealth.d.ts → core/providerHealth.d.ts} +15 -5
- package/dist/core/providerOrchestration.d.ts +109 -0
- package/dist/core/providerRouterTraces.d.ts +35 -0
- package/dist/core/providerRoutingContract.d.ts +71 -0
- package/dist/core/providerSlo.d.ts +142 -0
- package/dist/core/providerStackRecommendations.d.ts +188 -0
- package/dist/core/qualityDriftDetector.d.ts +44 -0
- package/dist/{qualityRoutes.d.ts → core/qualityRoutes.d.ts} +7 -7
- package/dist/{queue.d.ts → core/queue.d.ts} +48 -39
- package/dist/core/ragTool.d.ts +52 -0
- package/dist/core/readinessProfiles.d.ts +45 -0
- package/dist/core/realtimeChannel.d.ts +136 -0
- package/dist/core/realtimeProviderContracts.d.ts +133 -0
- package/dist/core/reconnectContract.d.ts +177 -0
- package/dist/core/recordingRedaction.d.ts +47 -0
- package/dist/core/recordingStore.d.ts +60 -0
- package/dist/core/redaction.d.ts +13 -0
- package/dist/core/reminderScheduler.d.ts +43 -0
- package/dist/{resilienceRoutes.d.ts → core/resilienceRoutes.d.ts} +45 -5
- package/dist/core/retention.d.ts +37 -0
- package/dist/core/retryPolicy.d.ts +38 -0
- package/dist/core/routeAuth.d.ts +58 -0
- package/dist/{routing.d.ts → core/routing.d.ts} +2 -2
- package/dist/{runtimeOps.d.ts → core/runtimeOps.d.ts} +3 -3
- package/dist/{s3Store.d.ts → core/s3Store.d.ts} +12 -3
- package/dist/core/scorecardCalibration.d.ts +31 -0
- package/dist/core/scribe.d.ts +50 -0
- package/dist/core/semanticTurn.d.ts +27 -0
- package/dist/{session.d.ts → core/session.d.ts} +1 -1
- package/dist/core/sessionObservability.d.ts +145 -0
- package/dist/{sessionReplay.d.ts → core/sessionReplay.d.ts} +31 -19
- package/dist/core/sessionSnapshot.d.ts +109 -0
- package/dist/core/simulationSuite.d.ts +144 -0
- package/dist/core/sloCalibration.d.ts +185 -0
- package/dist/{sqliteStore.d.ts → core/sqliteStore.d.ts} +20 -9
- package/dist/{store.d.ts → core/store.d.ts} +1 -1
- package/dist/core/supervisorPermissions.d.ts +33 -0
- package/dist/core/supervisorPresence.d.ts +49 -0
- package/dist/core/telephonyMediaRoutes.d.ts +72 -0
- package/dist/core/telephonyOutcome.d.ts +269 -0
- package/dist/core/toolContract.d.ts +161 -0
- package/dist/core/toolRuntime.d.ts +50 -0
- package/dist/{trace.d.ts → core/trace.d.ts} +61 -22
- package/dist/core/traceDeliveryRoutes.d.ts +86 -0
- package/dist/core/traceTimeline.d.ts +97 -0
- package/dist/core/transcriptAnnotator.d.ts +41 -0
- package/dist/{turnDetection.d.ts → core/turnDetection.d.ts} +1 -1
- package/dist/core/turnLatency.d.ts +95 -0
- package/dist/core/turnProfiles.d.ts +3 -0
- package/dist/core/turnQuality.d.ts +94 -0
- package/dist/{types.d.ts → core/types.d.ts} +545 -81
- package/dist/core/vapiAdapter.d.ts +160 -0
- package/dist/core/variableAnalytics.d.ts +47 -0
- package/dist/core/voiceConfiguration.d.ts +8 -0
- package/dist/core/voiceMonitoring.d.ts +444 -0
- package/dist/core/webhookFanout.d.ts +48 -0
- package/dist/core/webhookVerification.d.ts +27 -0
- package/dist/core/whisperChannel.d.ts +50 -0
- package/dist/{workflowContract.d.ts → core/workflowContract.d.ts} +21 -21
- package/dist/core/zeroDataRetention.d.ts +31 -0
- package/dist/drizzle/assistantMemory.d.ts +112 -0
- package/dist/drizzle/eval.d.ts +61 -0
- package/dist/drizzle/handoff.d.ts +62 -0
- package/dist/drizzle/incidentBundle.d.ts +61 -0
- package/dist/drizzle/index.d.ts +1105 -0
- package/dist/drizzle/index.js +3059 -0
- package/dist/drizzle/observabilityExport.d.ts +61 -0
- package/dist/drizzle/proofTrends.d.ts +126 -0
- package/dist/drizzle/runtimeStorage.d.ts +1315 -0
- package/dist/drizzle/shared.d.ts +76 -0
- package/dist/embed/index.d.ts +38 -0
- package/dist/embed/index.js +1734 -0
- package/dist/embed/voice-widget.js +10 -0
- package/dist/index.d.ts +373 -74
- package/dist/index.js +46333 -7422
- package/dist/internal/evidence.d.ts +10 -0
- package/dist/internal/html.d.ts +6 -0
- package/dist/internal/status.d.ts +7 -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 +80 -6
- package/dist/react/index.js +12949 -319
- 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 +5 -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 +5 -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/svelte/createVoiceAgentSquadStatus.d.ts +9 -0
- package/dist/svelte/createVoiceCallDebugger.d.ts +10 -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 +4 -4
- 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 +5 -3
- 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 +11 -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 +2 -2
- package/dist/svelte/index.d.ts +37 -6
- package/dist/svelte/index.js +7088 -765
- 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 +149 -15
- package/dist/testing/accuracy.d.ts +1 -1
- package/dist/testing/benchmark.d.ts +15 -15
- package/dist/testing/corrected.d.ts +9 -9
- package/dist/testing/duplex.d.ts +5 -5
- package/dist/testing/fixtures.d.ts +4 -4
- package/dist/testing/index.d.ts +13 -13
- package/dist/testing/index.js +6430 -894
- package/dist/testing/ioProviderSimulator.d.ts +5 -5
- package/dist/testing/providerSimulator.d.ts +5 -5
- package/dist/testing/review.d.ts +8 -8
- package/dist/testing/sessionBenchmark.d.ts +13 -13
- package/dist/testing/stt.d.ts +3 -3
- package/dist/testing/telephony.d.ts +27 -2
- package/dist/testing/tts.d.ts +2 -2
- package/dist/vue/VoiceCallDebuggerLaunch.d.ts +72 -0
- package/dist/vue/VoiceCallPlayer.d.ts +40 -0
- package/dist/vue/VoiceCostDashboard.d.ts +57 -0
- package/dist/vue/VoiceDeliveryRuntime.d.ts +34 -0
- package/dist/vue/VoiceLiveAgentConsole.d.ts +50 -0
- package/dist/vue/VoiceLiveCallViewer.d.ts +35 -0
- package/dist/vue/VoiceOpsActionCenter.d.ts +13 -0
- package/dist/vue/VoiceOpsStatus.d.ts +4 -0
- package/dist/vue/VoicePlatformCoverage.d.ts +27 -0
- package/dist/vue/VoiceProofTrends.d.ts +25 -0
- package/dist/vue/VoiceProviderCapabilities.d.ts +55 -0
- package/dist/vue/VoiceProviderContracts.d.ts +25 -0
- package/dist/vue/VoiceProviderSimulationControls.d.ts +88 -0
- package/dist/vue/VoiceProviderStatus.d.ts +55 -0
- package/dist/vue/VoiceReadinessFailures.d.ts +25 -0
- package/dist/vue/VoiceReconnectProfileEvidence.d.ts +25 -0
- package/dist/vue/VoiceReplayTimeline.d.ts +17 -0
- package/dist/vue/VoiceRoutingStatus.d.ts +55 -0
- package/dist/vue/VoiceSessionObservability.d.ts +27 -0
- package/dist/vue/VoiceSessionSnapshot.d.ts +72 -0
- package/dist/vue/VoiceTurnLatency.d.ts +73 -0
- package/dist/vue/VoiceTurnQuality.d.ts +55 -0
- package/dist/vue/VoiceWidget.d.ts +77 -0
- package/dist/vue/index.d.ts +49 -6
- package/dist/vue/index.js +12254 -413
- 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 +10 -7
- 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 +10 -6
- 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/package.json +155 -256
- package/dist/angular/voice-app-kit-status.service.d.ts +0 -12
- package/dist/angular/voice-ops-status.component.d.ts +0 -15
- 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/turnProfiles.d.ts +0 -6
- 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";
|
|
@@ -181,13 +184,25 @@ var serverMessageToAction = (message) => {
|
|
|
181
184
|
case "assistant":
|
|
182
185
|
return {
|
|
183
186
|
text: message.text,
|
|
187
|
+
turnId: message.turnId,
|
|
184
188
|
type: "assistant"
|
|
185
189
|
};
|
|
190
|
+
case "assistant_delta":
|
|
191
|
+
return {
|
|
192
|
+
delta: message.delta,
|
|
193
|
+
turnId: message.turnId,
|
|
194
|
+
type: "assistant_delta"
|
|
195
|
+
};
|
|
186
196
|
case "complete":
|
|
187
197
|
return {
|
|
188
198
|
sessionId: message.sessionId,
|
|
189
199
|
type: "complete"
|
|
190
200
|
};
|
|
201
|
+
case "connection":
|
|
202
|
+
return {
|
|
203
|
+
reconnect: message.reconnect,
|
|
204
|
+
type: "connection"
|
|
205
|
+
};
|
|
191
206
|
case "call_lifecycle":
|
|
192
207
|
return {
|
|
193
208
|
event: message.event,
|
|
@@ -209,9 +224,22 @@ var serverMessageToAction = (message) => {
|
|
|
209
224
|
transcript: message.transcript,
|
|
210
225
|
type: "partial"
|
|
211
226
|
};
|
|
227
|
+
case "replay":
|
|
228
|
+
return {
|
|
229
|
+
assistantTexts: message.assistantTexts,
|
|
230
|
+
call: message.call,
|
|
231
|
+
partial: message.partial,
|
|
232
|
+
scenarioId: message.scenarioId,
|
|
233
|
+
sessionId: message.sessionId,
|
|
234
|
+
sessionMetadata: message.sessionMetadata,
|
|
235
|
+
status: message.status,
|
|
236
|
+
turns: message.turns,
|
|
237
|
+
type: "replay"
|
|
238
|
+
};
|
|
212
239
|
case "session":
|
|
213
240
|
return {
|
|
214
241
|
sessionId: message.sessionId,
|
|
242
|
+
sessionMetadata: message.sessionMetadata,
|
|
215
243
|
scenarioId: message.scenarioId,
|
|
216
244
|
status: message.status,
|
|
217
245
|
type: "session"
|
|
@@ -226,6 +254,233 @@ var serverMessageToAction = (message) => {
|
|
|
226
254
|
}
|
|
227
255
|
};
|
|
228
256
|
|
|
257
|
+
// node_modules/@absolutejs/media/dist/index.js
|
|
258
|
+
var TAU = Math.PI * 2;
|
|
259
|
+
var pushIssue = (issues, severity, code, message) => {
|
|
260
|
+
issues.push({ code, message, severity });
|
|
261
|
+
};
|
|
262
|
+
var average = (values) => values.length === 0 ? undefined : values.reduce((total, value) => total + value, 0) / values.length;
|
|
263
|
+
var max = (values) => values.length === 0 ? undefined : Math.max(...values);
|
|
264
|
+
var numericStat = (stat, key) => {
|
|
265
|
+
const value = stat[key];
|
|
266
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
267
|
+
};
|
|
268
|
+
var booleanStat = (stat, key) => {
|
|
269
|
+
const value = stat[key];
|
|
270
|
+
return typeof value === "boolean" ? value : undefined;
|
|
271
|
+
};
|
|
272
|
+
var stringStat = (stat, key) => {
|
|
273
|
+
const value = stat[key];
|
|
274
|
+
return typeof value === "string" ? value : undefined;
|
|
275
|
+
};
|
|
276
|
+
var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
|
|
277
|
+
var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
|
|
278
|
+
var normalizeWebRTCStat = (stat) => {
|
|
279
|
+
const sample = {};
|
|
280
|
+
for (const [key, value] of Object.entries(stat)) {
|
|
281
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
282
|
+
sample[key] = value;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return sample;
|
|
286
|
+
};
|
|
287
|
+
var buildMediaWebRTCStatsReport = (input = {}) => {
|
|
288
|
+
const stats = input.stats ?? [];
|
|
289
|
+
const issues = [];
|
|
290
|
+
const inbound = stats.filter((stat) => stat.type === "inbound-rtp" && stringStat(stat, "kind") !== "video");
|
|
291
|
+
const outbound = stats.filter((stat) => stat.type === "outbound-rtp" && stringStat(stat, "kind") !== "video");
|
|
292
|
+
const candidatePairs = stats.filter((stat) => stat.type === "candidate-pair");
|
|
293
|
+
const audioTracks = stats.filter((stat) => (stat.type === "track" || stat.type === "media-source") && stringStat(stat, "kind") === "audio");
|
|
294
|
+
const activeCandidatePairs = candidatePairs.filter((stat) => booleanStat(stat, "selected") === true || booleanStat(stat, "nominated") === true || stringStat(stat, "state") === "succeeded").length;
|
|
295
|
+
const liveAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") !== "ended" && stringStat(stat, "trackState") !== "ended" && booleanStat(stat, "ended") !== true).length;
|
|
296
|
+
const endedAudioTracks = audioTracks.filter((stat) => stringStat(stat, "readyState") === "ended" || stringStat(stat, "trackState") === "ended" || booleanStat(stat, "ended") === true).length;
|
|
297
|
+
const inboundPackets = inbound.reduce((total, stat) => total + (numericStat(stat, "packetsReceived") ?? 0), 0);
|
|
298
|
+
const outboundPackets = outbound.reduce((total, stat) => total + (numericStat(stat, "packetsSent") ?? 0), 0);
|
|
299
|
+
const packetsLost = [...inbound, ...outbound].reduce((total, stat) => total + Math.max(0, numericStat(stat, "packetsLost") ?? 0), 0);
|
|
300
|
+
const packetLossDenominator = inboundPackets + packetsLost;
|
|
301
|
+
const packetLossRatio = packetLossDenominator === 0 ? 0 : packetsLost / packetLossDenominator;
|
|
302
|
+
const bytesReceived = inbound.reduce((total, stat) => total + (numericStat(stat, "bytesReceived") ?? 0), 0);
|
|
303
|
+
const bytesSent = outbound.reduce((total, stat) => total + (numericStat(stat, "bytesSent") ?? 0), 0);
|
|
304
|
+
const roundTripTimeMs = max(candidatePairs.map((stat) => secondsToMs(numericStat(stat, "currentRoundTripTime") ?? numericStat(stat, "roundTripTime"))).filter((value) => value !== undefined));
|
|
305
|
+
const jitterMs = max([...inbound, ...outbound].map((stat) => secondsToMs(numericStat(stat, "jitter"))).filter((value) => value !== undefined));
|
|
306
|
+
const jitterBufferDelayMs = max(inbound.map((stat) => {
|
|
307
|
+
const delay = numericStat(stat, "jitterBufferDelay");
|
|
308
|
+
const emitted = numericStat(stat, "jitterBufferEmittedCount");
|
|
309
|
+
return delay !== undefined && emitted !== undefined && emitted > 0 ? delay / emitted * 1000 : undefined;
|
|
310
|
+
}).filter((value) => value !== undefined));
|
|
311
|
+
const audioLevels = audioTracks.map((stat) => numericStat(stat, "audioLevel")).filter((value) => value !== undefined);
|
|
312
|
+
if (input.requireConnectedCandidatePair && candidatePairs.length > 0 && activeCandidatePairs === 0) {
|
|
313
|
+
pushIssue(issues, "error", "media.webrtc_candidate_pair_missing", "No active WebRTC candidate pair was observed.");
|
|
314
|
+
}
|
|
315
|
+
if (input.requireLiveAudioTrack && liveAudioTracks === 0) {
|
|
316
|
+
pushIssue(issues, "error", "media.webrtc_audio_track_missing", "No live WebRTC audio track was observed.");
|
|
317
|
+
}
|
|
318
|
+
if (input.maxPacketLossRatio !== undefined && packetLossRatio > input.maxPacketLossRatio) {
|
|
319
|
+
pushIssue(issues, "warning", "media.webrtc_packet_loss", `Observed WebRTC packet loss ratio ${String(packetLossRatio)} above ${String(input.maxPacketLossRatio)}.`);
|
|
320
|
+
}
|
|
321
|
+
if (input.maxRoundTripTimeMs !== undefined && roundTripTimeMs !== undefined && roundTripTimeMs > input.maxRoundTripTimeMs) {
|
|
322
|
+
pushIssue(issues, "warning", "media.webrtc_round_trip_time", `Observed WebRTC RTT ${String(roundTripTimeMs)}ms above ${String(input.maxRoundTripTimeMs)}ms.`);
|
|
323
|
+
}
|
|
324
|
+
if (input.maxJitterMs !== undefined && jitterMs !== undefined && jitterMs > input.maxJitterMs) {
|
|
325
|
+
pushIssue(issues, "warning", "media.webrtc_jitter", `Observed WebRTC jitter ${String(jitterMs)}ms above ${String(input.maxJitterMs)}ms.`);
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
activeCandidatePairs,
|
|
329
|
+
audioLevelAverage: average(audioLevels),
|
|
330
|
+
bytesReceived,
|
|
331
|
+
bytesSent,
|
|
332
|
+
checkedAt: Date.now(),
|
|
333
|
+
endedAudioTracks,
|
|
334
|
+
inboundPackets,
|
|
335
|
+
issues,
|
|
336
|
+
jitterBufferDelayMs,
|
|
337
|
+
jitterMs,
|
|
338
|
+
liveAudioTracks,
|
|
339
|
+
outboundPackets,
|
|
340
|
+
packetLossRatio,
|
|
341
|
+
packetsLost,
|
|
342
|
+
roundTripTimeMs,
|
|
343
|
+
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
|
|
344
|
+
totalStats: stats.length
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
var collectMediaWebRTCStats = async (input) => {
|
|
348
|
+
const report = await input.peerConnection.getStats(input.selector ?? null);
|
|
349
|
+
return [...report.values()].map(normalizeWebRTCStat);
|
|
350
|
+
};
|
|
351
|
+
var buildMediaWebRTCStreamContinuityReport = (input = {}) => {
|
|
352
|
+
const stats = input.stats ?? [];
|
|
353
|
+
const previousStats = input.previousStats ?? [];
|
|
354
|
+
const issues = [];
|
|
355
|
+
const previousByKey = new Map(previousStats.map((stat) => [statKey(stat), stat]));
|
|
356
|
+
const audioRtp = stats.filter((stat) => (stat.type === "inbound-rtp" || stat.type === "outbound-rtp") && stringStat(stat, "kind") !== "video" && stringStat(stat, "mediaType") !== "video");
|
|
357
|
+
const streams = audioRtp.map((stat) => {
|
|
358
|
+
const direction = stat.type === "outbound-rtp" ? "outbound" : "inbound";
|
|
359
|
+
const packetsKey = direction === "outbound" ? "packetsSent" : "packetsReceived";
|
|
360
|
+
const bytesKey = direction === "outbound" ? "bytesSent" : "bytesReceived";
|
|
361
|
+
const previous = previousByKey.get(statKey(stat));
|
|
362
|
+
const currentPackets = numericStat(stat, packetsKey);
|
|
363
|
+
const previousPackets = previous ? numericStat(previous, packetsKey) : undefined;
|
|
364
|
+
const currentBytes = numericStat(stat, bytesKey);
|
|
365
|
+
const previousBytes = previous ? numericStat(previous, bytesKey) : undefined;
|
|
366
|
+
const timeDeltaMs = stat.timestamp !== undefined && previous?.timestamp !== undefined ? stat.timestamp - previous.timestamp : undefined;
|
|
367
|
+
return {
|
|
368
|
+
bytesDelta: currentBytes !== undefined && previousBytes !== undefined ? currentBytes - previousBytes : undefined,
|
|
369
|
+
currentPackets,
|
|
370
|
+
direction,
|
|
371
|
+
id: statKey(stat),
|
|
372
|
+
packetDelta: currentPackets !== undefined && previousPackets !== undefined ? currentPackets - previousPackets : undefined,
|
|
373
|
+
previousPackets,
|
|
374
|
+
timeDeltaMs
|
|
375
|
+
};
|
|
376
|
+
});
|
|
377
|
+
const inbound = streams.filter((stream) => stream.direction === "inbound");
|
|
378
|
+
const outbound = streams.filter((stream) => stream.direction === "outbound");
|
|
379
|
+
const maxObservedGapMs = max(streams.map((stream) => stream.timeDeltaMs).filter((value) => value !== undefined));
|
|
380
|
+
const stalledInboundStreams = inbound.filter((stream) => input.maxInboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxInboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
|
|
381
|
+
const stalledOutboundStreams = outbound.filter((stream) => input.maxOutboundPacketStallMs !== undefined && stream.timeDeltaMs !== undefined && stream.timeDeltaMs >= input.maxOutboundPacketStallMs && stream.packetDelta !== undefined && stream.packetDelta <= 0).length;
|
|
382
|
+
if (input.requireInboundAudio && inbound.length === 0) {
|
|
383
|
+
pushIssue(issues, "error", "media.webrtc_inbound_audio_missing", "No inbound WebRTC audio RTP stream was observed.");
|
|
384
|
+
}
|
|
385
|
+
if (input.requireOutboundAudio && outbound.length === 0) {
|
|
386
|
+
pushIssue(issues, "error", "media.webrtc_outbound_audio_missing", "No outbound WebRTC audio RTP stream was observed.");
|
|
387
|
+
}
|
|
388
|
+
if (input.maxGapMs !== undefined && maxObservedGapMs !== undefined && maxObservedGapMs > input.maxGapMs) {
|
|
389
|
+
pushIssue(issues, "warning", "media.webrtc_stream_gap", `Observed WebRTC stream sample gap ${String(maxObservedGapMs)}ms above ${String(input.maxGapMs)}ms.`);
|
|
390
|
+
}
|
|
391
|
+
if (stalledInboundStreams > 0) {
|
|
392
|
+
pushIssue(issues, "error", "media.webrtc_inbound_stalled", `${String(stalledInboundStreams)} inbound WebRTC audio stream(s) stopped receiving packets.`);
|
|
393
|
+
}
|
|
394
|
+
if (stalledOutboundStreams > 0) {
|
|
395
|
+
pushIssue(issues, "error", "media.webrtc_outbound_stalled", `${String(stalledOutboundStreams)} outbound WebRTC audio stream(s) stopped sending packets.`);
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
checkedAt: Date.now(),
|
|
399
|
+
inboundAudioStreams: inbound.length,
|
|
400
|
+
issues,
|
|
401
|
+
maxObservedGapMs,
|
|
402
|
+
outboundAudioStreams: outbound.length,
|
|
403
|
+
stalledInboundStreams,
|
|
404
|
+
stalledOutboundStreams,
|
|
405
|
+
status: issues.some((issue) => issue.severity === "error") ? "fail" : issues.length > 0 ? "warn" : "pass",
|
|
406
|
+
streams,
|
|
407
|
+
totalStats: stats.length
|
|
408
|
+
};
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// src/client/browserMedia.ts
|
|
412
|
+
var DEFAULT_BROWSER_MEDIA_PATH = "/api/voice/browser-media";
|
|
413
|
+
var DEFAULT_BROWSER_MEDIA_INTERVAL_MS = 5000;
|
|
414
|
+
var resolvePeerConnection = async (options) => options.peerConnection ?? await options.getPeerConnection?.() ?? null;
|
|
415
|
+
var postBrowserMediaReport = async (payload, options) => {
|
|
416
|
+
const requestFetch = options.fetch ?? globalThis.fetch;
|
|
417
|
+
if (!requestFetch) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
await requestFetch(options.path ?? DEFAULT_BROWSER_MEDIA_PATH, {
|
|
421
|
+
body: JSON.stringify(payload),
|
|
422
|
+
headers: {
|
|
423
|
+
"Content-Type": "application/json"
|
|
424
|
+
},
|
|
425
|
+
keepalive: true,
|
|
426
|
+
method: "POST"
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
var createVoiceBrowserMediaReporter = (options) => {
|
|
430
|
+
let interval = null;
|
|
431
|
+
let previousStats = [];
|
|
432
|
+
const reportOnce = async () => {
|
|
433
|
+
const peerConnection = await resolvePeerConnection(options);
|
|
434
|
+
if (!peerConnection) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const stats = await collectMediaWebRTCStats({ peerConnection });
|
|
438
|
+
const report = buildMediaWebRTCStatsReport({
|
|
439
|
+
...options,
|
|
440
|
+
stats
|
|
441
|
+
});
|
|
442
|
+
const continuity = options.continuity === false ? undefined : buildMediaWebRTCStreamContinuityReport({
|
|
443
|
+
...options.continuity,
|
|
444
|
+
previousStats,
|
|
445
|
+
stats
|
|
446
|
+
});
|
|
447
|
+
const payload = {
|
|
448
|
+
at: Date.now(),
|
|
449
|
+
continuity,
|
|
450
|
+
report,
|
|
451
|
+
scenarioId: options.getScenarioId?.() ?? null,
|
|
452
|
+
sessionId: options.getSessionId?.() ?? null
|
|
453
|
+
};
|
|
454
|
+
previousStats = stats;
|
|
455
|
+
options.onReport?.(payload);
|
|
456
|
+
await postBrowserMediaReport(payload, options);
|
|
457
|
+
return payload;
|
|
458
|
+
};
|
|
459
|
+
const run = () => {
|
|
460
|
+
reportOnce().catch((error) => {
|
|
461
|
+
options.onError?.(error);
|
|
462
|
+
});
|
|
463
|
+
};
|
|
464
|
+
const stop = () => {
|
|
465
|
+
if (interval) {
|
|
466
|
+
clearInterval(interval);
|
|
467
|
+
interval = null;
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
return {
|
|
471
|
+
close: stop,
|
|
472
|
+
reportOnce,
|
|
473
|
+
stop,
|
|
474
|
+
start: () => {
|
|
475
|
+
if (interval) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
run();
|
|
479
|
+
interval = setInterval(run, options.intervalMs ?? DEFAULT_BROWSER_MEDIA_INTERVAL_MS);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
};
|
|
483
|
+
|
|
229
484
|
// src/client/connection.ts
|
|
230
485
|
var WS_OPEN = 1;
|
|
231
486
|
var WS_CLOSED = 3;
|
|
@@ -240,13 +495,14 @@ var NOOP_CONNECTION = {
|
|
|
240
495
|
callControl: noop,
|
|
241
496
|
close: noop,
|
|
242
497
|
endTurn: noop,
|
|
498
|
+
send: noop,
|
|
499
|
+
sendAudio: noop,
|
|
500
|
+
simulateDisconnect: noop,
|
|
501
|
+
subscribe: noopUnsubscribe,
|
|
243
502
|
getReadyState: () => WS_CLOSED,
|
|
244
503
|
getScenarioId: () => "",
|
|
245
504
|
getSessionId: () => "",
|
|
246
|
-
|
|
247
|
-
sendAudio: noop,
|
|
248
|
-
start: () => {},
|
|
249
|
-
subscribe: noopUnsubscribe
|
|
505
|
+
start: () => {}
|
|
250
506
|
};
|
|
251
507
|
var createSessionId = () => crypto.randomUUID();
|
|
252
508
|
var buildWsUrl = (path, sessionId, scenarioId) => {
|
|
@@ -269,10 +525,12 @@ var isVoiceServerMessage = (value) => {
|
|
|
269
525
|
case "assistant":
|
|
270
526
|
case "call_lifecycle":
|
|
271
527
|
case "complete":
|
|
528
|
+
case "connection":
|
|
272
529
|
case "error":
|
|
273
530
|
case "final":
|
|
274
531
|
case "partial":
|
|
275
532
|
case "pong":
|
|
533
|
+
case "replay":
|
|
276
534
|
case "session":
|
|
277
535
|
case "turn":
|
|
278
536
|
return true;
|
|
@@ -309,6 +567,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
309
567
|
sessionId: options.sessionId ?? createSessionId(),
|
|
310
568
|
ws: null
|
|
311
569
|
};
|
|
570
|
+
const emitConnection = (reconnect) => {
|
|
571
|
+
listeners.forEach((listener) => listener(reconnect));
|
|
572
|
+
};
|
|
312
573
|
const clearTimers = () => {
|
|
313
574
|
if (state.pingInterval) {
|
|
314
575
|
clearInterval(state.pingInterval);
|
|
@@ -331,9 +592,28 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
331
592
|
}
|
|
332
593
|
};
|
|
333
594
|
const scheduleReconnect = () => {
|
|
595
|
+
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
334
596
|
state.reconnectAttempts += 1;
|
|
597
|
+
emitConnection({
|
|
598
|
+
reconnect: {
|
|
599
|
+
attempts: state.reconnectAttempts,
|
|
600
|
+
lastDisconnectAt: Date.now(),
|
|
601
|
+
maxAttempts: maxReconnectAttempts,
|
|
602
|
+
nextAttemptAt,
|
|
603
|
+
status: "reconnecting"
|
|
604
|
+
},
|
|
605
|
+
type: "connection"
|
|
606
|
+
});
|
|
335
607
|
state.reconnectTimeout = setTimeout(() => {
|
|
336
608
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
609
|
+
emitConnection({
|
|
610
|
+
reconnect: {
|
|
611
|
+
attempts: state.reconnectAttempts,
|
|
612
|
+
maxAttempts: maxReconnectAttempts,
|
|
613
|
+
status: "exhausted"
|
|
614
|
+
},
|
|
615
|
+
type: "connection"
|
|
616
|
+
});
|
|
337
617
|
return;
|
|
338
618
|
}
|
|
339
619
|
connect();
|
|
@@ -343,9 +623,21 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
343
623
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
|
344
624
|
ws.binaryType = "arraybuffer";
|
|
345
625
|
ws.onopen = () => {
|
|
626
|
+
const wasReconnecting = state.reconnectAttempts > 0;
|
|
346
627
|
state.isConnected = true;
|
|
347
|
-
state.reconnectAttempts = 0;
|
|
348
628
|
flushPendingMessages();
|
|
629
|
+
if (wasReconnecting) {
|
|
630
|
+
emitConnection({
|
|
631
|
+
reconnect: {
|
|
632
|
+
attempts: state.reconnectAttempts,
|
|
633
|
+
lastResumedAt: Date.now(),
|
|
634
|
+
maxAttempts: maxReconnectAttempts,
|
|
635
|
+
status: "resumed"
|
|
636
|
+
},
|
|
637
|
+
type: "connection"
|
|
638
|
+
});
|
|
639
|
+
state.reconnectAttempts = 0;
|
|
640
|
+
}
|
|
349
641
|
listeners.forEach((listener) => listener({
|
|
350
642
|
scenarioId: state.scenarioId ?? undefined,
|
|
351
643
|
sessionId: state.sessionId,
|
|
@@ -375,6 +667,16 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
375
667
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
376
668
|
if (reconnectable) {
|
|
377
669
|
scheduleReconnect();
|
|
670
|
+
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
671
|
+
emitConnection({
|
|
672
|
+
reconnect: {
|
|
673
|
+
attempts: state.reconnectAttempts,
|
|
674
|
+
lastDisconnectAt: Date.now(),
|
|
675
|
+
maxAttempts: maxReconnectAttempts,
|
|
676
|
+
status: "exhausted"
|
|
677
|
+
},
|
|
678
|
+
type: "connection"
|
|
679
|
+
});
|
|
378
680
|
}
|
|
379
681
|
};
|
|
380
682
|
state.ws = ws;
|
|
@@ -397,9 +699,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
397
699
|
state.scenarioId = input.scenarioId;
|
|
398
700
|
}
|
|
399
701
|
send({
|
|
400
|
-
|
|
702
|
+
scenarioId: state.scenarioId ?? undefined,
|
|
401
703
|
sessionId: state.sessionId,
|
|
402
|
-
|
|
704
|
+
type: "start"
|
|
403
705
|
});
|
|
404
706
|
};
|
|
405
707
|
const sendAudio = (audio) => {
|
|
@@ -423,6 +725,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
423
725
|
state.isConnected = false;
|
|
424
726
|
listeners.clear();
|
|
425
727
|
};
|
|
728
|
+
const simulateDisconnect = () => {
|
|
729
|
+
if (state.ws?.readyState === WS_OPEN) {
|
|
730
|
+
state.ws.close(4000, "absolutejs-voice-reconnect-proof");
|
|
731
|
+
}
|
|
732
|
+
};
|
|
426
733
|
const subscribe = (callback) => {
|
|
427
734
|
listeners.add(callback);
|
|
428
735
|
return () => {
|
|
@@ -434,26 +741,35 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
434
741
|
callControl,
|
|
435
742
|
close,
|
|
436
743
|
endTurn,
|
|
437
|
-
getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
|
|
438
|
-
getScenarioId: () => state.scenarioId ?? "",
|
|
439
|
-
getSessionId: () => state.sessionId,
|
|
440
744
|
send,
|
|
441
745
|
sendAudio,
|
|
746
|
+
simulateDisconnect,
|
|
442
747
|
start,
|
|
443
|
-
subscribe
|
|
748
|
+
subscribe,
|
|
749
|
+
getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
|
|
750
|
+
getScenarioId: () => state.scenarioId ?? "",
|
|
751
|
+
getSessionId: () => state.sessionId
|
|
444
752
|
};
|
|
445
753
|
};
|
|
446
754
|
|
|
447
755
|
// src/client/store.ts
|
|
756
|
+
var createInitialReconnectState = () => ({
|
|
757
|
+
attempts: 0,
|
|
758
|
+
maxAttempts: 0,
|
|
759
|
+
status: "idle"
|
|
760
|
+
});
|
|
448
761
|
var createInitialState = () => ({
|
|
449
762
|
assistantAudio: [],
|
|
763
|
+
assistantStreamingText: "",
|
|
450
764
|
assistantTexts: [],
|
|
451
765
|
call: null,
|
|
452
766
|
error: null,
|
|
453
767
|
isConnected: false,
|
|
454
|
-
scenarioId: null,
|
|
455
768
|
partial: "",
|
|
769
|
+
reconnect: createInitialReconnectState(),
|
|
770
|
+
scenarioId: null,
|
|
456
771
|
sessionId: null,
|
|
772
|
+
sessionMetadata: null,
|
|
457
773
|
status: "idle",
|
|
458
774
|
turns: []
|
|
459
775
|
});
|
|
@@ -482,9 +798,16 @@ var createVoiceStreamStore = () => {
|
|
|
482
798
|
case "assistant":
|
|
483
799
|
state = {
|
|
484
800
|
...state,
|
|
801
|
+
assistantStreamingText: "",
|
|
485
802
|
assistantTexts: [...state.assistantTexts, action.text]
|
|
486
803
|
};
|
|
487
804
|
break;
|
|
805
|
+
case "assistant_delta":
|
|
806
|
+
state = {
|
|
807
|
+
...state,
|
|
808
|
+
assistantStreamingText: `${state.assistantStreamingText}${action.delta}`
|
|
809
|
+
};
|
|
810
|
+
break;
|
|
488
811
|
case "complete":
|
|
489
812
|
state = {
|
|
490
813
|
...state,
|
|
@@ -509,7 +832,19 @@ var createVoiceStreamStore = () => {
|
|
|
509
832
|
case "connected":
|
|
510
833
|
state = {
|
|
511
834
|
...state,
|
|
512
|
-
isConnected: true
|
|
835
|
+
isConnected: true,
|
|
836
|
+
reconnect: state.reconnect.status === "reconnecting" ? {
|
|
837
|
+
...state.reconnect,
|
|
838
|
+
lastResumedAt: Date.now(),
|
|
839
|
+
nextAttemptAt: undefined,
|
|
840
|
+
status: "resumed"
|
|
841
|
+
} : state.reconnect
|
|
842
|
+
};
|
|
843
|
+
break;
|
|
844
|
+
case "connection":
|
|
845
|
+
state = {
|
|
846
|
+
...state,
|
|
847
|
+
reconnect: action.reconnect
|
|
513
848
|
};
|
|
514
849
|
break;
|
|
515
850
|
case "disconnected":
|
|
@@ -537,6 +872,28 @@ var createVoiceStreamStore = () => {
|
|
|
537
872
|
partial: action.transcript.text
|
|
538
873
|
};
|
|
539
874
|
break;
|
|
875
|
+
case "replay":
|
|
876
|
+
state = {
|
|
877
|
+
...state,
|
|
878
|
+
assistantStreamingText: "",
|
|
879
|
+
assistantTexts: [...action.assistantTexts],
|
|
880
|
+
call: action.call ?? null,
|
|
881
|
+
error: null,
|
|
882
|
+
isConnected: action.status === "active",
|
|
883
|
+
partial: action.partial,
|
|
884
|
+
reconnect: state.reconnect.status === "reconnecting" ? {
|
|
885
|
+
...state.reconnect,
|
|
886
|
+
lastResumedAt: Date.now(),
|
|
887
|
+
nextAttemptAt: undefined,
|
|
888
|
+
status: "resumed"
|
|
889
|
+
} : state.reconnect,
|
|
890
|
+
scenarioId: action.scenarioId ?? state.scenarioId,
|
|
891
|
+
sessionId: action.sessionId,
|
|
892
|
+
sessionMetadata: action.sessionMetadata ?? state.sessionMetadata,
|
|
893
|
+
status: action.status,
|
|
894
|
+
turns: [...action.turns]
|
|
895
|
+
};
|
|
896
|
+
break;
|
|
540
897
|
case "session":
|
|
541
898
|
state = {
|
|
542
899
|
...state,
|
|
@@ -544,6 +901,7 @@ var createVoiceStreamStore = () => {
|
|
|
544
901
|
scenarioId: action.scenarioId ?? state.scenarioId,
|
|
545
902
|
isConnected: action.status === "active",
|
|
546
903
|
sessionId: action.sessionId,
|
|
904
|
+
sessionMetadata: action.sessionMetadata ?? state.sessionMetadata,
|
|
547
905
|
status: action.status
|
|
548
906
|
};
|
|
549
907
|
break;
|
|
@@ -574,29 +932,73 @@ var createVoiceStreamStore = () => {
|
|
|
574
932
|
var createVoiceStream = (path, options = {}) => {
|
|
575
933
|
const connection = createVoiceConnection(path, options);
|
|
576
934
|
const store = createVoiceStreamStore();
|
|
935
|
+
const browserMediaReporter = options.browserMedia && typeof window !== "undefined" ? createVoiceBrowserMediaReporter({
|
|
936
|
+
...options.browserMedia,
|
|
937
|
+
getScenarioId: () => options.browserMedia ? options.browserMedia.getScenarioId?.() ?? connection.getScenarioId() : connection.getScenarioId(),
|
|
938
|
+
getSessionId: () => options.browserMedia ? options.browserMedia.getSessionId?.() ?? connection.getSessionId() : connection.getSessionId()
|
|
939
|
+
}) : null;
|
|
577
940
|
const subscribers = new Set;
|
|
578
941
|
const start = (input) => Promise.resolve().then(() => {
|
|
579
942
|
if (!input?.sessionId && !input?.scenarioId) {
|
|
580
943
|
return;
|
|
581
944
|
}
|
|
582
945
|
connection.start(input);
|
|
946
|
+
browserMediaReporter?.start();
|
|
583
947
|
});
|
|
584
948
|
const notify = () => {
|
|
585
949
|
subscribers.forEach((subscriber) => subscriber());
|
|
586
950
|
};
|
|
951
|
+
const reportReconnect = () => {
|
|
952
|
+
if (!options.reconnectReportPath || typeof fetch === "undefined") {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const snapshot = store.getSnapshot();
|
|
956
|
+
const body = JSON.stringify({
|
|
957
|
+
at: Date.now(),
|
|
958
|
+
reconnect: snapshot.reconnect,
|
|
959
|
+
scenarioId: snapshot.scenarioId,
|
|
960
|
+
sessionId: connection.getSessionId(),
|
|
961
|
+
turnIds: snapshot.turns.map((turn) => turn.id)
|
|
962
|
+
});
|
|
963
|
+
fetch(options.reconnectReportPath, {
|
|
964
|
+
body,
|
|
965
|
+
headers: {
|
|
966
|
+
"Content-Type": "application/json"
|
|
967
|
+
},
|
|
968
|
+
keepalive: true,
|
|
969
|
+
method: "POST"
|
|
970
|
+
}).catch(() => {});
|
|
971
|
+
};
|
|
587
972
|
const unsubscribeConnection = connection.subscribe((message) => {
|
|
588
973
|
const action = serverMessageToAction(message);
|
|
589
974
|
if (action) {
|
|
590
975
|
store.dispatch(action);
|
|
976
|
+
if (message.type === "connection") {
|
|
977
|
+
reportReconnect();
|
|
978
|
+
}
|
|
591
979
|
notify();
|
|
592
980
|
}
|
|
593
981
|
});
|
|
594
982
|
return {
|
|
983
|
+
start,
|
|
984
|
+
get assistantAudio() {
|
|
985
|
+
return store.getSnapshot().assistantAudio;
|
|
986
|
+
},
|
|
987
|
+
get assistantTexts() {
|
|
988
|
+
return store.getSnapshot().assistantTexts;
|
|
989
|
+
},
|
|
990
|
+
get assistantStreamingText() {
|
|
991
|
+
return store.getSnapshot().assistantStreamingText;
|
|
992
|
+
},
|
|
993
|
+
get call() {
|
|
994
|
+
return store.getSnapshot().call;
|
|
995
|
+
},
|
|
595
996
|
callControl(message) {
|
|
596
997
|
connection.callControl(message);
|
|
597
998
|
},
|
|
598
999
|
close() {
|
|
599
1000
|
unsubscribeConnection();
|
|
1001
|
+
browserMediaReporter?.close();
|
|
600
1002
|
connection.close();
|
|
601
1003
|
store.dispatch({ type: "disconnected" });
|
|
602
1004
|
notify();
|
|
@@ -616,44 +1018,43 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
616
1018
|
get isConnected() {
|
|
617
1019
|
return store.getSnapshot().isConnected;
|
|
618
1020
|
},
|
|
619
|
-
get scenarioId() {
|
|
620
|
-
return store.getSnapshot().scenarioId;
|
|
621
|
-
},
|
|
622
|
-
start,
|
|
623
1021
|
get partial() {
|
|
624
1022
|
return store.getSnapshot().partial;
|
|
625
1023
|
},
|
|
626
|
-
get
|
|
627
|
-
return
|
|
1024
|
+
get reconnect() {
|
|
1025
|
+
return store.getSnapshot().reconnect;
|
|
628
1026
|
},
|
|
629
|
-
get
|
|
630
|
-
return store.getSnapshot().
|
|
1027
|
+
get scenarioId() {
|
|
1028
|
+
return store.getSnapshot().scenarioId;
|
|
631
1029
|
},
|
|
632
|
-
|
|
633
|
-
|
|
1030
|
+
sendAudio(audio) {
|
|
1031
|
+
connection.sendAudio(audio);
|
|
634
1032
|
},
|
|
635
|
-
get
|
|
636
|
-
return
|
|
1033
|
+
get sessionId() {
|
|
1034
|
+
return connection.getSessionId();
|
|
637
1035
|
},
|
|
638
|
-
get
|
|
639
|
-
return store.getSnapshot().
|
|
1036
|
+
get sessionMetadata() {
|
|
1037
|
+
return store.getSnapshot().sessionMetadata;
|
|
640
1038
|
},
|
|
641
|
-
|
|
642
|
-
|
|
1039
|
+
simulateDisconnect() {
|
|
1040
|
+
connection.simulateDisconnect();
|
|
643
1041
|
},
|
|
644
|
-
|
|
645
|
-
|
|
1042
|
+
get status() {
|
|
1043
|
+
return store.getSnapshot().status;
|
|
646
1044
|
},
|
|
647
1045
|
subscribe(subscriber) {
|
|
648
1046
|
subscribers.add(subscriber);
|
|
649
1047
|
return () => {
|
|
650
1048
|
subscribers.delete(subscriber);
|
|
651
1049
|
};
|
|
1050
|
+
},
|
|
1051
|
+
get turns() {
|
|
1052
|
+
return store.getSnapshot().turns;
|
|
652
1053
|
}
|
|
653
1054
|
};
|
|
654
1055
|
};
|
|
655
1056
|
|
|
656
|
-
// src/audioConditioning.ts
|
|
1057
|
+
// src/core/audioConditioning.ts
|
|
657
1058
|
var DEFAULT_TARGET_LEVEL = 0.08;
|
|
658
1059
|
var DEFAULT_MAX_GAIN = 3;
|
|
659
1060
|
var DEFAULT_NOISE_GATE_THRESHOLD = 0.006;
|
|
@@ -671,7 +1072,7 @@ var resolveAudioConditioningConfig = (config) => {
|
|
|
671
1072
|
};
|
|
672
1073
|
};
|
|
673
1074
|
|
|
674
|
-
// src/turnProfiles.ts
|
|
1075
|
+
// src/core/turnProfiles.ts
|
|
675
1076
|
var TURN_PROFILE_DEFAULTS = {
|
|
676
1077
|
balanced: {
|
|
677
1078
|
qualityProfile: "general",
|
|
@@ -693,12 +1094,12 @@ var TURN_PROFILE_DEFAULTS = {
|
|
|
693
1094
|
}
|
|
694
1095
|
};
|
|
695
1096
|
var QUALITY_PROFILE_DEFAULTS = {
|
|
696
|
-
general: {},
|
|
697
1097
|
"accent-heavy": {
|
|
698
1098
|
silenceMs: 1200,
|
|
699
1099
|
speechThreshold: 0.01,
|
|
700
1100
|
transcriptStabilityMs: 1200
|
|
701
1101
|
},
|
|
1102
|
+
general: {},
|
|
702
1103
|
"noisy-room": {
|
|
703
1104
|
silenceMs: 2000,
|
|
704
1105
|
speechThreshold: 0.02,
|
|
@@ -726,7 +1127,7 @@ var resolveTurnDetectionConfig = (config) => {
|
|
|
726
1127
|
};
|
|
727
1128
|
};
|
|
728
1129
|
|
|
729
|
-
// src/presets.ts
|
|
1130
|
+
// src/core/presets.ts
|
|
730
1131
|
var PRESET_INPUTS = {
|
|
731
1132
|
chat: {
|
|
732
1133
|
audioConditioning: {
|
|
@@ -747,8 +1148,8 @@ var PRESET_INPUTS = {
|
|
|
747
1148
|
},
|
|
748
1149
|
sttLifecycle: "continuous",
|
|
749
1150
|
turnDetection: {
|
|
750
|
-
|
|
751
|
-
|
|
1151
|
+
profile: "balanced",
|
|
1152
|
+
qualityProfile: "short-command"
|
|
752
1153
|
}
|
|
753
1154
|
},
|
|
754
1155
|
default: {
|
|
@@ -763,8 +1164,8 @@ var PRESET_INPUTS = {
|
|
|
763
1164
|
},
|
|
764
1165
|
sttLifecycle: "continuous",
|
|
765
1166
|
turnDetection: {
|
|
766
|
-
|
|
767
|
-
|
|
1167
|
+
profile: "fast",
|
|
1168
|
+
qualityProfile: "general"
|
|
768
1169
|
}
|
|
769
1170
|
},
|
|
770
1171
|
dictation: {
|
|
@@ -786,8 +1187,8 @@ var PRESET_INPUTS = {
|
|
|
786
1187
|
},
|
|
787
1188
|
sttLifecycle: "continuous",
|
|
788
1189
|
turnDetection: {
|
|
789
|
-
|
|
790
|
-
|
|
1190
|
+
profile: "long-form",
|
|
1191
|
+
qualityProfile: "accent-heavy"
|
|
791
1192
|
}
|
|
792
1193
|
},
|
|
793
1194
|
"guided-intake": {
|
|
@@ -809,8 +1210,8 @@ var PRESET_INPUTS = {
|
|
|
809
1210
|
},
|
|
810
1211
|
sttLifecycle: "turn-scoped",
|
|
811
1212
|
turnDetection: {
|
|
812
|
-
|
|
813
|
-
|
|
1213
|
+
profile: "long-form",
|
|
1214
|
+
qualityProfile: "accent-heavy"
|
|
814
1215
|
}
|
|
815
1216
|
},
|
|
816
1217
|
"noisy-room": {
|
|
@@ -832,8 +1233,8 @@ var PRESET_INPUTS = {
|
|
|
832
1233
|
},
|
|
833
1234
|
sttLifecycle: "continuous",
|
|
834
1235
|
turnDetection: {
|
|
835
|
-
qualityProfile: "noisy-room",
|
|
836
1236
|
profile: "long-form",
|
|
1237
|
+
qualityProfile: "noisy-room",
|
|
837
1238
|
silenceMs: 2100,
|
|
838
1239
|
speechThreshold: 0.02,
|
|
839
1240
|
transcriptStabilityMs: 1650
|
|
@@ -858,8 +1259,8 @@ var PRESET_INPUTS = {
|
|
|
858
1259
|
},
|
|
859
1260
|
sttLifecycle: "continuous",
|
|
860
1261
|
turnDetection: {
|
|
861
|
-
qualityProfile: "noisy-room",
|
|
862
1262
|
profile: "long-form",
|
|
1263
|
+
qualityProfile: "noisy-room",
|
|
863
1264
|
silenceMs: 660,
|
|
864
1265
|
speechThreshold: 0.012,
|
|
865
1266
|
transcriptStabilityMs: 300
|
|
@@ -884,8 +1285,8 @@ var PRESET_INPUTS = {
|
|
|
884
1285
|
},
|
|
885
1286
|
sttLifecycle: "continuous",
|
|
886
1287
|
turnDetection: {
|
|
887
|
-
qualityProfile: "noisy-room",
|
|
888
1288
|
profile: "long-form",
|
|
1289
|
+
qualityProfile: "noisy-room",
|
|
889
1290
|
silenceMs: 620,
|
|
890
1291
|
speechThreshold: 0.012,
|
|
891
1292
|
transcriptStabilityMs: 280
|
|
@@ -910,8 +1311,8 @@ var PRESET_INPUTS = {
|
|
|
910
1311
|
},
|
|
911
1312
|
sttLifecycle: "continuous",
|
|
912
1313
|
turnDetection: {
|
|
913
|
-
|
|
914
|
-
|
|
1314
|
+
profile: "long-form",
|
|
1315
|
+
qualityProfile: "noisy-room"
|
|
915
1316
|
}
|
|
916
1317
|
}
|
|
917
1318
|
};
|
|
@@ -935,14 +1336,17 @@ var resolveVoiceRuntimePreset = (name = "default") => {
|
|
|
935
1336
|
// src/client/controller.ts
|
|
936
1337
|
var createInitialState2 = (stream) => ({
|
|
937
1338
|
assistantAudio: [...stream.assistantAudio],
|
|
1339
|
+
assistantStreamingText: stream.assistantStreamingText,
|
|
938
1340
|
assistantTexts: [...stream.assistantTexts],
|
|
939
1341
|
call: stream.call,
|
|
940
1342
|
error: stream.error,
|
|
941
1343
|
isConnected: stream.isConnected,
|
|
942
1344
|
isRecording: false,
|
|
943
1345
|
partial: stream.partial,
|
|
1346
|
+
reconnect: stream.reconnect,
|
|
944
1347
|
recordingError: null,
|
|
945
1348
|
sessionId: stream.sessionId,
|
|
1349
|
+
sessionMetadata: stream.sessionMetadata,
|
|
946
1350
|
scenarioId: stream.scenarioId,
|
|
947
1351
|
status: stream.status,
|
|
948
1352
|
turns: [...stream.turns]
|
|
@@ -965,12 +1369,15 @@ var createVoiceController = (path, options = {}) => {
|
|
|
965
1369
|
state = {
|
|
966
1370
|
...state,
|
|
967
1371
|
assistantAudio: [...stream.assistantAudio],
|
|
1372
|
+
assistantStreamingText: stream.assistantStreamingText,
|
|
968
1373
|
assistantTexts: [...stream.assistantTexts],
|
|
969
1374
|
call: stream.call,
|
|
970
1375
|
error: stream.error,
|
|
971
1376
|
isConnected: stream.isConnected,
|
|
972
1377
|
partial: stream.partial,
|
|
1378
|
+
reconnect: stream.reconnect,
|
|
973
1379
|
sessionId: stream.sessionId,
|
|
1380
|
+
sessionMetadata: stream.sessionMetadata,
|
|
974
1381
|
scenarioId: stream.scenarioId,
|
|
975
1382
|
status: stream.status,
|
|
976
1383
|
turns: [...stream.turns]
|
|
@@ -994,7 +1401,13 @@ var createVoiceController = (path, options = {}) => {
|
|
|
994
1401
|
capture = createMicrophoneCapture({
|
|
995
1402
|
channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
|
|
996
1403
|
onLevel: options.capture?.onLevel,
|
|
997
|
-
onAudio: (audio) =>
|
|
1404
|
+
onAudio: (audio) => {
|
|
1405
|
+
if (options.capture?.onAudio) {
|
|
1406
|
+
options.capture.onAudio(audio, stream.sendAudio);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
stream.sendAudio(audio);
|
|
1410
|
+
},
|
|
998
1411
|
sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
|
|
999
1412
|
});
|
|
1000
1413
|
return capture;
|
|
@@ -1041,11 +1454,25 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1041
1454
|
stream.close();
|
|
1042
1455
|
};
|
|
1043
1456
|
return {
|
|
1457
|
+
close,
|
|
1458
|
+
startRecording,
|
|
1459
|
+
stopRecording,
|
|
1460
|
+
get assistantAudio() {
|
|
1461
|
+
return state.assistantAudio;
|
|
1462
|
+
},
|
|
1463
|
+
get assistantTexts() {
|
|
1464
|
+
return state.assistantTexts;
|
|
1465
|
+
},
|
|
1466
|
+
get assistantStreamingText() {
|
|
1467
|
+
return state.assistantStreamingText;
|
|
1468
|
+
},
|
|
1044
1469
|
bindHTMX(bindingOptions) {
|
|
1045
1470
|
return bindVoiceHTMX(stream, bindingOptions);
|
|
1046
1471
|
},
|
|
1472
|
+
get call() {
|
|
1473
|
+
return state.call;
|
|
1474
|
+
},
|
|
1047
1475
|
callControl: (message) => stream.callControl(message),
|
|
1048
|
-
close,
|
|
1049
1476
|
endTurn: () => stream.endTurn(),
|
|
1050
1477
|
get error() {
|
|
1051
1478
|
return state.error;
|
|
@@ -1061,21 +1488,26 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1061
1488
|
get partial() {
|
|
1062
1489
|
return state.partial;
|
|
1063
1490
|
},
|
|
1491
|
+
get reconnect() {
|
|
1492
|
+
return state.reconnect;
|
|
1493
|
+
},
|
|
1064
1494
|
get recordingError() {
|
|
1065
1495
|
return state.recordingError;
|
|
1066
1496
|
},
|
|
1497
|
+
get scenarioId() {
|
|
1498
|
+
return state.scenarioId;
|
|
1499
|
+
},
|
|
1067
1500
|
sendAudio: (audio) => stream.sendAudio(audio),
|
|
1068
1501
|
get sessionId() {
|
|
1069
1502
|
return state.sessionId;
|
|
1070
1503
|
},
|
|
1071
|
-
get
|
|
1072
|
-
return state.
|
|
1504
|
+
get sessionMetadata() {
|
|
1505
|
+
return state.sessionMetadata;
|
|
1073
1506
|
},
|
|
1074
|
-
|
|
1507
|
+
simulateDisconnect: () => stream.simulateDisconnect(),
|
|
1075
1508
|
get status() {
|
|
1076
1509
|
return state.status;
|
|
1077
1510
|
},
|
|
1078
|
-
stopRecording,
|
|
1079
1511
|
subscribe: (subscriber) => {
|
|
1080
1512
|
subscribers.add(subscriber);
|
|
1081
1513
|
return () => {
|
|
@@ -1091,15 +1523,490 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1091
1523
|
},
|
|
1092
1524
|
get turns() {
|
|
1093
1525
|
return state.turns;
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
// src/client/audioPlayer.ts
|
|
1531
|
+
var DEFAULT_LOOKAHEAD_MS = 15;
|
|
1532
|
+
var DEFAULT_VOLUME = 1;
|
|
1533
|
+
var createInitialState3 = () => ({
|
|
1534
|
+
activeSourceCount: 0,
|
|
1535
|
+
error: null,
|
|
1536
|
+
isActive: false,
|
|
1537
|
+
isPlaying: false,
|
|
1538
|
+
lastInterruptLatencyMs: undefined,
|
|
1539
|
+
lastPlaybackStopLatencyMs: undefined,
|
|
1540
|
+
processedChunkCount: 0,
|
|
1541
|
+
queuedChunkCount: 0
|
|
1542
|
+
});
|
|
1543
|
+
var getAudioContextCtor = () => {
|
|
1544
|
+
if (typeof window === "undefined") {
|
|
1545
|
+
return typeof AudioContext === "undefined" ? undefined : AudioContext;
|
|
1546
|
+
}
|
|
1547
|
+
return window.AudioContext ?? window.webkitAudioContext;
|
|
1548
|
+
};
|
|
1549
|
+
var clampVolume = (volume) => {
|
|
1550
|
+
if (typeof volume !== "number" || !Number.isFinite(volume)) {
|
|
1551
|
+
return DEFAULT_VOLUME;
|
|
1552
|
+
}
|
|
1553
|
+
return Math.min(1, Math.max(0, volume));
|
|
1554
|
+
};
|
|
1555
|
+
var decodePCM16LEChunk = (audioContext, chunk) => {
|
|
1556
|
+
const { format } = chunk;
|
|
1557
|
+
if (format.container !== "raw" || format.encoding !== "pcm_s16le") {
|
|
1558
|
+
throw new Error(`Unsupported assistant audio format: ${format.container}/${format.encoding}`);
|
|
1559
|
+
}
|
|
1560
|
+
const bytes = chunk.chunk;
|
|
1561
|
+
const channels = Math.max(1, format.channels);
|
|
1562
|
+
const sampleCount = Math.floor(bytes.byteLength / 2);
|
|
1563
|
+
const frameCount = Math.max(1, Math.floor(sampleCount / channels));
|
|
1564
|
+
const audioBuffer = audioContext.createBuffer(channels, frameCount, format.sampleRateHz);
|
|
1565
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1566
|
+
for (let channelIndex = 0;channelIndex < channels; channelIndex += 1) {
|
|
1567
|
+
const channelData = audioBuffer.getChannelData(channelIndex);
|
|
1568
|
+
for (let frameIndex = 0;frameIndex < frameCount; frameIndex += 1) {
|
|
1569
|
+
const sampleIndex = frameIndex * channels + channelIndex;
|
|
1570
|
+
const sampleOffset = sampleIndex * 2;
|
|
1571
|
+
if (sampleOffset + 1 >= bytes.byteLength) {
|
|
1572
|
+
channelData[frameIndex] = 0;
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
channelData[frameIndex] = view.getInt16(sampleOffset, true) / 32768;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return audioBuffer;
|
|
1579
|
+
};
|
|
1580
|
+
var createVoiceAudioPlayer = (source, options = {}) => {
|
|
1581
|
+
const subscribers = new Set;
|
|
1582
|
+
const sourceNodes = new Set;
|
|
1583
|
+
const lookaheadSeconds = (options.lookaheadMs ?? DEFAULT_LOOKAHEAD_MS) / 1000;
|
|
1584
|
+
let state = createInitialState3();
|
|
1585
|
+
let audioContext = null;
|
|
1586
|
+
let outputNode = null;
|
|
1587
|
+
let volume = clampVolume(options.volume);
|
|
1588
|
+
let queueEndTime = 0;
|
|
1589
|
+
let syncPromise = Promise.resolve();
|
|
1590
|
+
let interruptStartedAt = null;
|
|
1591
|
+
let interruptPromise = null;
|
|
1592
|
+
let resolveInterruptPromise = null;
|
|
1593
|
+
let interruptFallbackTimer = null;
|
|
1594
|
+
const notify = () => {
|
|
1595
|
+
for (const subscriber of subscribers) {
|
|
1596
|
+
subscriber();
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
const setState = (next) => {
|
|
1600
|
+
state = {
|
|
1601
|
+
...state,
|
|
1602
|
+
...next
|
|
1603
|
+
};
|
|
1604
|
+
notify();
|
|
1605
|
+
};
|
|
1606
|
+
const clearError = () => {
|
|
1607
|
+
if (state.error !== null) {
|
|
1608
|
+
setState({ error: null });
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
const clearInterruptTimer = () => {
|
|
1612
|
+
if (interruptFallbackTimer !== null) {
|
|
1613
|
+
clearTimeout(interruptFallbackTimer);
|
|
1614
|
+
interruptFallbackTimer = null;
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
1617
|
+
const resolveInterrupt = (latencyMs) => {
|
|
1618
|
+
clearInterruptTimer();
|
|
1619
|
+
interruptStartedAt = null;
|
|
1620
|
+
setState({
|
|
1621
|
+
activeSourceCount: sourceNodes.size,
|
|
1622
|
+
isPlaying: false,
|
|
1623
|
+
lastInterruptLatencyMs: latencyMs,
|
|
1624
|
+
lastPlaybackStopLatencyMs: state.lastPlaybackStopLatencyMs ?? latencyMs
|
|
1625
|
+
});
|
|
1626
|
+
resolveInterruptPromise?.();
|
|
1627
|
+
resolveInterruptPromise = null;
|
|
1628
|
+
interruptPromise = null;
|
|
1629
|
+
};
|
|
1630
|
+
const estimateOutputStopLatencyMs = (context) => {
|
|
1631
|
+
if (!context) {
|
|
1632
|
+
return 0;
|
|
1633
|
+
}
|
|
1634
|
+
return Math.max(0, ((context.baseLatency ?? 0) + (context.outputLatency ?? 0)) * 1000);
|
|
1635
|
+
};
|
|
1636
|
+
const applyOutputGain = (context) => {
|
|
1637
|
+
if (!outputNode) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
const gainValue = volume;
|
|
1641
|
+
if (outputNode.gain.setValueAtTime) {
|
|
1642
|
+
outputNode.gain.setValueAtTime(gainValue, context?.currentTime ?? 0);
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
outputNode.gain.value = gainValue;
|
|
1646
|
+
};
|
|
1647
|
+
const muteOutputGain = (context) => {
|
|
1648
|
+
if (!outputNode) {
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
const gainValue = 0;
|
|
1652
|
+
if (outputNode.gain.setValueAtTime) {
|
|
1653
|
+
outputNode.gain.setValueAtTime(gainValue, context?.currentTime ?? 0);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
outputNode.gain.value = gainValue;
|
|
1657
|
+
};
|
|
1658
|
+
const maybeResolveInterrupt = () => {
|
|
1659
|
+
if (interruptStartedAt === null || sourceNodes.size > 0) {
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
resolveInterrupt(Date.now() - interruptStartedAt);
|
|
1663
|
+
};
|
|
1664
|
+
const ensureAudioContext = async () => {
|
|
1665
|
+
if (audioContext) {
|
|
1666
|
+
return audioContext;
|
|
1667
|
+
}
|
|
1668
|
+
if (options.createAudioContext) {
|
|
1669
|
+
audioContext = options.createAudioContext();
|
|
1670
|
+
} else {
|
|
1671
|
+
const AudioContextCtor = getAudioContextCtor();
|
|
1672
|
+
if (!AudioContextCtor) {
|
|
1673
|
+
throw new Error("Assistant audio playback requires AudioContext support.");
|
|
1674
|
+
}
|
|
1675
|
+
audioContext = new AudioContextCtor;
|
|
1676
|
+
}
|
|
1677
|
+
if (audioContext.createGain) {
|
|
1678
|
+
outputNode = audioContext.createGain();
|
|
1679
|
+
outputNode.connect?.(audioContext.destination);
|
|
1680
|
+
}
|
|
1681
|
+
queueEndTime = audioContext.currentTime;
|
|
1682
|
+
return audioContext;
|
|
1683
|
+
};
|
|
1684
|
+
const scheduleChunk = async (chunk) => {
|
|
1685
|
+
const context = await ensureAudioContext();
|
|
1686
|
+
const buffer = decodePCM16LEChunk(context, chunk);
|
|
1687
|
+
const node = context.createBufferSource();
|
|
1688
|
+
node.buffer = buffer;
|
|
1689
|
+
node.connect(outputNode ?? context.destination);
|
|
1690
|
+
node.onended = () => {
|
|
1691
|
+
sourceNodes.delete(node);
|
|
1692
|
+
node.disconnect?.();
|
|
1693
|
+
setState({
|
|
1694
|
+
activeSourceCount: sourceNodes.size,
|
|
1695
|
+
isPlaying: sourceNodes.size > 0 && state.isActive
|
|
1696
|
+
});
|
|
1697
|
+
maybeResolveInterrupt();
|
|
1698
|
+
};
|
|
1699
|
+
const startAt = Math.max(context.currentTime + lookaheadSeconds, queueEndTime);
|
|
1700
|
+
queueEndTime = startAt + buffer.duration;
|
|
1701
|
+
sourceNodes.add(node);
|
|
1702
|
+
setState({
|
|
1703
|
+
activeSourceCount: sourceNodes.size,
|
|
1704
|
+
isPlaying: true
|
|
1705
|
+
});
|
|
1706
|
+
node.start(startAt);
|
|
1707
|
+
};
|
|
1708
|
+
const stopQueuedPlayback = (options2) => {
|
|
1709
|
+
for (const node of [...sourceNodes]) {
|
|
1710
|
+
node.stop?.();
|
|
1711
|
+
}
|
|
1712
|
+
queueEndTime = audioContext ? audioContext.currentTime : 0;
|
|
1713
|
+
if (options2?.forceClear) {
|
|
1714
|
+
for (const node of sourceNodes) {
|
|
1715
|
+
node.disconnect?.();
|
|
1716
|
+
}
|
|
1717
|
+
sourceNodes.clear();
|
|
1718
|
+
maybeResolveInterrupt();
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
const sync = async () => {
|
|
1722
|
+
if (!state.isActive) {
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
const nextChunks = source.assistantAudio.slice(state.processedChunkCount);
|
|
1726
|
+
if (nextChunks.length === 0) {
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
try {
|
|
1730
|
+
clearError();
|
|
1731
|
+
for (const chunk of nextChunks) {
|
|
1732
|
+
await scheduleChunk(chunk);
|
|
1733
|
+
}
|
|
1734
|
+
setState({
|
|
1735
|
+
processedChunkCount: source.assistantAudio.length,
|
|
1736
|
+
queuedChunkCount: state.queuedChunkCount + nextChunks.length
|
|
1737
|
+
});
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
setState({
|
|
1740
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
const queueSync = () => {
|
|
1745
|
+
syncPromise = syncPromise.then(() => sync(), () => sync());
|
|
1746
|
+
return syncPromise;
|
|
1747
|
+
};
|
|
1748
|
+
const unsubscribeSource = source.subscribe(() => {
|
|
1749
|
+
if (options.autoStart && !state.isActive && source.assistantAudio.length > 0) {
|
|
1750
|
+
player.start();
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
if (state.isActive) {
|
|
1754
|
+
queueSync();
|
|
1755
|
+
}
|
|
1756
|
+
});
|
|
1757
|
+
const player = {
|
|
1758
|
+
get activeSourceCount() {
|
|
1759
|
+
return state.activeSourceCount;
|
|
1094
1760
|
},
|
|
1095
|
-
|
|
1096
|
-
|
|
1761
|
+
close: async () => {
|
|
1762
|
+
unsubscribeSource();
|
|
1763
|
+
stopQueuedPlayback({ forceClear: true });
|
|
1764
|
+
clearInterruptTimer();
|
|
1765
|
+
resolveInterruptPromise?.();
|
|
1766
|
+
resolveInterruptPromise = null;
|
|
1767
|
+
interruptPromise = null;
|
|
1768
|
+
interruptStartedAt = null;
|
|
1769
|
+
if (audioContext && audioContext.state !== "closed") {
|
|
1770
|
+
await audioContext.close();
|
|
1771
|
+
}
|
|
1772
|
+
audioContext = null;
|
|
1773
|
+
outputNode?.disconnect?.();
|
|
1774
|
+
outputNode = null;
|
|
1775
|
+
queueEndTime = 0;
|
|
1776
|
+
setState({
|
|
1777
|
+
activeSourceCount: 0,
|
|
1778
|
+
isActive: false,
|
|
1779
|
+
isPlaying: false
|
|
1780
|
+
});
|
|
1097
1781
|
},
|
|
1098
|
-
get
|
|
1099
|
-
return state.
|
|
1782
|
+
get error() {
|
|
1783
|
+
return state.error;
|
|
1100
1784
|
},
|
|
1101
|
-
|
|
1102
|
-
|
|
1785
|
+
getSnapshot: () => state,
|
|
1786
|
+
interrupt: async () => {
|
|
1787
|
+
const startedAt = Date.now();
|
|
1788
|
+
const context = await ensureAudioContext();
|
|
1789
|
+
interruptStartedAt = startedAt;
|
|
1790
|
+
muteOutputGain(context);
|
|
1791
|
+
const playbackStopLatencyMs = Date.now() - startedAt + estimateOutputStopLatencyMs(context);
|
|
1792
|
+
setState({
|
|
1793
|
+
isActive: false,
|
|
1794
|
+
isPlaying: sourceNodes.size > 0,
|
|
1795
|
+
lastPlaybackStopLatencyMs: playbackStopLatencyMs
|
|
1796
|
+
});
|
|
1797
|
+
if (sourceNodes.size === 0) {
|
|
1798
|
+
resolveInterrupt(playbackStopLatencyMs);
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
if (!interruptPromise) {
|
|
1802
|
+
interruptPromise = new Promise((resolve) => {
|
|
1803
|
+
resolveInterruptPromise = resolve;
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
clearInterruptTimer();
|
|
1807
|
+
interruptFallbackTimer = setTimeout(() => {
|
|
1808
|
+
for (const node of sourceNodes) {
|
|
1809
|
+
node.disconnect?.();
|
|
1810
|
+
}
|
|
1811
|
+
sourceNodes.clear();
|
|
1812
|
+
resolveInterrupt(Date.now() - startedAt);
|
|
1813
|
+
}, 250);
|
|
1814
|
+
stopQueuedPlayback();
|
|
1815
|
+
await interruptPromise;
|
|
1816
|
+
},
|
|
1817
|
+
get isActive() {
|
|
1818
|
+
return state.isActive;
|
|
1819
|
+
},
|
|
1820
|
+
get isPlaying() {
|
|
1821
|
+
return state.isPlaying;
|
|
1822
|
+
},
|
|
1823
|
+
get lastInterruptLatencyMs() {
|
|
1824
|
+
return state.lastInterruptLatencyMs;
|
|
1825
|
+
},
|
|
1826
|
+
get lastPlaybackStopLatencyMs() {
|
|
1827
|
+
return state.lastPlaybackStopLatencyMs;
|
|
1828
|
+
},
|
|
1829
|
+
pause: async () => {
|
|
1830
|
+
if (!audioContext) {
|
|
1831
|
+
setState({
|
|
1832
|
+
activeSourceCount: 0,
|
|
1833
|
+
isActive: false,
|
|
1834
|
+
isPlaying: false
|
|
1835
|
+
});
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
await audioContext.suspend();
|
|
1839
|
+
setState({
|
|
1840
|
+
activeSourceCount: sourceNodes.size,
|
|
1841
|
+
isActive: false,
|
|
1842
|
+
isPlaying: false
|
|
1843
|
+
});
|
|
1844
|
+
},
|
|
1845
|
+
get processedChunkCount() {
|
|
1846
|
+
return state.processedChunkCount;
|
|
1847
|
+
},
|
|
1848
|
+
get queuedChunkCount() {
|
|
1849
|
+
return state.queuedChunkCount;
|
|
1850
|
+
},
|
|
1851
|
+
setVolume: (nextVolume) => {
|
|
1852
|
+
volume = clampVolume(nextVolume);
|
|
1853
|
+
applyOutputGain(audioContext);
|
|
1854
|
+
},
|
|
1855
|
+
start: async () => {
|
|
1856
|
+
try {
|
|
1857
|
+
clearError();
|
|
1858
|
+
const context = await ensureAudioContext();
|
|
1859
|
+
applyOutputGain(context);
|
|
1860
|
+
if (context.state === "suspended") {
|
|
1861
|
+
await context.resume();
|
|
1862
|
+
}
|
|
1863
|
+
setState({
|
|
1864
|
+
activeSourceCount: sourceNodes.size,
|
|
1865
|
+
isActive: true,
|
|
1866
|
+
isPlaying: context.state === "running"
|
|
1867
|
+
});
|
|
1868
|
+
await queueSync();
|
|
1869
|
+
} catch (error) {
|
|
1870
|
+
setState({
|
|
1871
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1872
|
+
isActive: false,
|
|
1873
|
+
isPlaying: false
|
|
1874
|
+
});
|
|
1875
|
+
throw error;
|
|
1876
|
+
}
|
|
1877
|
+
},
|
|
1878
|
+
subscribe: (subscriber) => {
|
|
1879
|
+
subscribers.add(subscriber);
|
|
1880
|
+
return () => {
|
|
1881
|
+
subscribers.delete(subscriber);
|
|
1882
|
+
};
|
|
1883
|
+
},
|
|
1884
|
+
get volume() {
|
|
1885
|
+
return volume;
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1888
|
+
return player;
|
|
1889
|
+
};
|
|
1890
|
+
|
|
1891
|
+
// src/client/bargeInMonitor.ts
|
|
1892
|
+
var DEFAULT_THRESHOLD_MS = 250;
|
|
1893
|
+
var createEventId = () => `barge-in:${Date.now()}:${crypto.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
1894
|
+
var summarize = (events, thresholdMs) => {
|
|
1895
|
+
const stopped = events.filter((event) => event.status === "stopped");
|
|
1896
|
+
const latencies = stopped.map((event) => event.latencyMs).filter((value) => typeof value === "number");
|
|
1897
|
+
const failed = stopped.filter((event) => typeof event.latencyMs === "number" && event.latencyMs > thresholdMs).length;
|
|
1898
|
+
const passed = stopped.length - failed;
|
|
1899
|
+
return {
|
|
1900
|
+
averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
|
|
1901
|
+
events: [...events],
|
|
1902
|
+
failed,
|
|
1903
|
+
lastEvent: events.at(-1),
|
|
1904
|
+
passed,
|
|
1905
|
+
status: events.length === 0 ? "empty" : failed > 0 ? "fail" : stopped.length === 0 ? "warn" : "pass",
|
|
1906
|
+
thresholdMs,
|
|
1907
|
+
total: stopped.length
|
|
1908
|
+
};
|
|
1909
|
+
};
|
|
1910
|
+
var createVoiceBargeInMonitor = (options = {}) => {
|
|
1911
|
+
const listeners = new Set;
|
|
1912
|
+
const thresholdMs = options.thresholdMs ?? DEFAULT_THRESHOLD_MS;
|
|
1913
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1914
|
+
const events = [];
|
|
1915
|
+
const emit = () => {
|
|
1916
|
+
for (const listener of listeners) {
|
|
1917
|
+
listener();
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
const postEvent = (event) => {
|
|
1921
|
+
if (!options.path || typeof fetchImpl !== "function") {
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
fetchImpl(options.path, {
|
|
1925
|
+
body: JSON.stringify(event),
|
|
1926
|
+
headers: {
|
|
1927
|
+
"Content-Type": "application/json"
|
|
1928
|
+
},
|
|
1929
|
+
method: "POST"
|
|
1930
|
+
}).catch(() => {});
|
|
1931
|
+
};
|
|
1932
|
+
const record = (status, input) => {
|
|
1933
|
+
const event = {
|
|
1934
|
+
at: Date.now(),
|
|
1935
|
+
id: createEventId(),
|
|
1936
|
+
latencyMs: input.latencyMs,
|
|
1937
|
+
playbackStopLatencyMs: input.playbackStopLatencyMs,
|
|
1938
|
+
reason: input.reason,
|
|
1939
|
+
sessionId: input.sessionId,
|
|
1940
|
+
status,
|
|
1941
|
+
thresholdMs
|
|
1942
|
+
};
|
|
1943
|
+
events.push(event);
|
|
1944
|
+
postEvent(event);
|
|
1945
|
+
emit();
|
|
1946
|
+
return event;
|
|
1947
|
+
};
|
|
1948
|
+
return {
|
|
1949
|
+
getSnapshot: () => summarize(events, thresholdMs),
|
|
1950
|
+
recordRequested: (input) => record("requested", input),
|
|
1951
|
+
recordSkipped: (input) => record("skipped", input),
|
|
1952
|
+
recordStopped: (input) => record("stopped", input),
|
|
1953
|
+
subscribe: (subscriber) => {
|
|
1954
|
+
listeners.add(subscriber);
|
|
1955
|
+
return () => {
|
|
1956
|
+
listeners.delete(subscriber);
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
};
|
|
1961
|
+
|
|
1962
|
+
// src/client/duplex.ts
|
|
1963
|
+
var DEFAULT_INTERRUPT_THRESHOLD = 0.08;
|
|
1964
|
+
var shouldInterruptForLevel = (level, options = {}) => (options.enabled ?? true) && level >= (options.interruptThreshold ?? DEFAULT_INTERRUPT_THRESHOLD);
|
|
1965
|
+
var bindVoiceBargeIn = (controller, player, options = {}) => {
|
|
1966
|
+
let lastPartial = controller.partial;
|
|
1967
|
+
const interruptIfPlaying = (reason) => {
|
|
1968
|
+
if (!player.isPlaying || options.enabled === false) {
|
|
1969
|
+
options.monitor?.recordSkipped({
|
|
1970
|
+
reason,
|
|
1971
|
+
sessionId: controller.sessionId
|
|
1972
|
+
});
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
options.monitor?.recordRequested({
|
|
1976
|
+
reason,
|
|
1977
|
+
sessionId: controller.sessionId
|
|
1978
|
+
});
|
|
1979
|
+
player.interrupt().then(() => {
|
|
1980
|
+
options.monitor?.recordStopped({
|
|
1981
|
+
latencyMs: player.lastInterruptLatencyMs,
|
|
1982
|
+
playbackStopLatencyMs: player.lastPlaybackStopLatencyMs,
|
|
1983
|
+
reason,
|
|
1984
|
+
sessionId: controller.sessionId
|
|
1985
|
+
});
|
|
1986
|
+
});
|
|
1987
|
+
};
|
|
1988
|
+
const unsubscribe = controller.subscribe(() => {
|
|
1989
|
+
if (options.interruptOnPartial === false) {
|
|
1990
|
+
lastPartial = controller.partial;
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
if (!lastPartial && controller.partial) {
|
|
1994
|
+
interruptIfPlaying("partial-transcript");
|
|
1995
|
+
}
|
|
1996
|
+
lastPartial = controller.partial;
|
|
1997
|
+
});
|
|
1998
|
+
return {
|
|
1999
|
+
close: () => {
|
|
2000
|
+
unsubscribe();
|
|
2001
|
+
},
|
|
2002
|
+
handleLevel: (level) => {
|
|
2003
|
+
if (shouldInterruptForLevel(level, options)) {
|
|
2004
|
+
interruptIfPlaying("input-level");
|
|
2005
|
+
}
|
|
2006
|
+
},
|
|
2007
|
+
sendAudio: (audio) => {
|
|
2008
|
+
interruptIfPlaying("manual-audio");
|
|
2009
|
+
controller.sendAudio(audio);
|
|
1103
2010
|
}
|
|
1104
2011
|
};
|
|
1105
2012
|
};
|
|
@@ -1126,8 +2033,7 @@ var DEFAULT_GUIDED_PROMPTS = [
|
|
|
1126
2033
|
"Now describe what you are trying to do or test.",
|
|
1127
2034
|
"Finish with any detail that feels blocked, risky, or unclear."
|
|
1128
2035
|
];
|
|
1129
|
-
var clamp = (value, min,
|
|
1130
|
-
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
2036
|
+
var clamp = (value, min, max2) => Math.min(max2, Math.max(min, value));
|
|
1131
2037
|
var readErrorField = (value, key) => {
|
|
1132
2038
|
const candidate = value[key];
|
|
1133
2039
|
if (typeof candidate === "string" && candidate.trim()) {
|
|
@@ -1160,6 +2066,17 @@ var formatErrorMessage = (error) => {
|
|
|
1160
2066
|
}
|
|
1161
2067
|
return "Unexpected error";
|
|
1162
2068
|
};
|
|
2069
|
+
var formatReconnectState = (reconnect) => {
|
|
2070
|
+
const pieces = [reconnect.status];
|
|
2071
|
+
if (reconnect.attempts > 0 || reconnect.maxAttempts > 0) {
|
|
2072
|
+
pieces.push(`${reconnect.attempts}/${reconnect.maxAttempts} attempts`);
|
|
2073
|
+
}
|
|
2074
|
+
if (reconnect.nextAttemptAt) {
|
|
2075
|
+
const waitMs = Math.max(0, reconnect.nextAttemptAt - Date.now());
|
|
2076
|
+
pieces.push(`retry in ${Math.ceil(waitMs / 100) / 10}s`);
|
|
2077
|
+
}
|
|
2078
|
+
return pieces.join(" · ");
|
|
2079
|
+
};
|
|
1163
2080
|
var createInitialVoiceWaveLevels = (count = VOICE_WAVE_POINTS) => Array.from({ length: count }, () => 0);
|
|
1164
2081
|
var pushVoiceWaveLevel = (levels, nextLevel, count = VOICE_WAVE_POINTS) => {
|
|
1165
2082
|
const next = levels.slice(-(count - 1));
|
|
@@ -1216,6 +2133,20 @@ var parsePromptList = (value) => {
|
|
|
1216
2133
|
} catch {}
|
|
1217
2134
|
return DEFAULT_GUIDED_PROMPTS;
|
|
1218
2135
|
};
|
|
2136
|
+
var parseOptionalNumber = (value) => {
|
|
2137
|
+
if (!value) {
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
const parsed = Number(value);
|
|
2141
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
2142
|
+
};
|
|
2143
|
+
var resolveElement2 = (root, selector, ctor) => {
|
|
2144
|
+
if (!selector) {
|
|
2145
|
+
return null;
|
|
2146
|
+
}
|
|
2147
|
+
const value = document.querySelector(selector);
|
|
2148
|
+
return value instanceof ctor ? value : null;
|
|
2149
|
+
};
|
|
1219
2150
|
var requireElement = (root, selector, ctor, name) => {
|
|
1220
2151
|
const value = selector ? document.querySelector(selector) : null;
|
|
1221
2152
|
if (value instanceof ctor) {
|
|
@@ -1266,11 +2197,20 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1266
2197
|
const guidedPrompts = parsePromptList(root.dataset.voiceGuidedPrompts);
|
|
1267
2198
|
const guidedLabel = root.dataset.voiceGuidedLabel ?? DEFAULT_GUIDED_LABEL;
|
|
1268
2199
|
const generalLabel = root.dataset.voiceGeneralLabel ?? DEFAULT_GENERAL_LABEL;
|
|
2200
|
+
const reconnectReportPath = root.dataset.voiceReconnectReportPath;
|
|
2201
|
+
const bargeInPath = root.dataset.voiceBargeInPath;
|
|
2202
|
+
const bargeInMonitor = bargeInPath ? createVoiceBargeInMonitor({
|
|
2203
|
+
path: bargeInPath,
|
|
2204
|
+
thresholdMs: parseOptionalNumber(root.dataset.voiceBargeInThresholdMs)
|
|
2205
|
+
}) : null;
|
|
2206
|
+
const bargeInRecentWindowMs = parseOptionalNumber(root.dataset.voiceBargeInRecentWindowMs) ?? 4000;
|
|
2207
|
+
const bargeInSpeechThreshold = parseOptionalNumber(root.dataset.voiceBargeInSpeechThreshold) ?? 0.04;
|
|
1269
2208
|
const syncElement = requireElement(document, root.dataset.voiceSync, HTMLElement, "voice-htmx-sync");
|
|
1270
2209
|
const connectionMetric = requireElement(root, root.dataset.voiceConnection, HTMLElement, "metric-connection");
|
|
1271
2210
|
const errorStatus = requireElement(root, root.dataset.voiceError, HTMLElement, "status-error");
|
|
1272
2211
|
const microphoneStatus = requireElement(root, root.dataset.voiceMicrophone, HTMLElement, "status-mic");
|
|
1273
2212
|
const promptStatus = requireElement(root, root.dataset.voicePrompt, HTMLElement, "status-prompt");
|
|
2213
|
+
const reconnectStatus = resolveElement2(root, root.dataset.voiceReconnect, HTMLElement);
|
|
1274
2214
|
const chatList = requireElement(root, root.dataset.voiceChat, HTMLElement, "chat-list");
|
|
1275
2215
|
const startGuidedButton = requireElement(root, root.dataset.voiceStartGuided, HTMLButtonElement, "start-guided");
|
|
1276
2216
|
const startGeneralButton = requireElement(root, root.dataset.voiceStartGeneral, HTMLButtonElement, "start-general");
|
|
@@ -1279,35 +2219,70 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1279
2219
|
const voiceMonitorCopy = requireElement(root, root.dataset.voiceMonitorCopy, HTMLElement, "voice-monitor-copy");
|
|
1280
2220
|
const voiceWaveGlow = requireElement(root, root.dataset.voiceWaveGlow, SVGPathElement, "voice-wave-glow");
|
|
1281
2221
|
const voiceWavePath = requireElement(root, root.dataset.voiceWavePath, SVGPathElement, "voice-wave-path");
|
|
2222
|
+
let activeMode = null;
|
|
2223
|
+
let hasStartedModes = {
|
|
2224
|
+
general: false,
|
|
2225
|
+
guided: false
|
|
2226
|
+
};
|
|
2227
|
+
let isCapturing = false;
|
|
2228
|
+
let micError = null;
|
|
2229
|
+
let waveLevels = createInitialVoiceWaveLevels();
|
|
2230
|
+
let guidedBargeInBinding = null;
|
|
2231
|
+
let generalBargeInBinding = null;
|
|
1282
2232
|
const guidedVoice = createVoiceController(guidedPath, {
|
|
1283
2233
|
capture: {
|
|
2234
|
+
onAudio: (audio, sendAudio) => {
|
|
2235
|
+
if (guidedBargeInBinding) {
|
|
2236
|
+
guidedBargeInBinding.sendAudio(audio);
|
|
2237
|
+
return;
|
|
2238
|
+
}
|
|
2239
|
+
sendAudio(audio);
|
|
2240
|
+
},
|
|
1284
2241
|
onLevel: (level) => {
|
|
2242
|
+
guidedBargeInBinding?.handleLevel(level);
|
|
1285
2243
|
waveLevels = pushVoiceWaveLevel(waveLevels, level);
|
|
1286
2244
|
renderWave();
|
|
1287
2245
|
}
|
|
1288
2246
|
},
|
|
2247
|
+
connection: {
|
|
2248
|
+
reconnectReportPath
|
|
2249
|
+
},
|
|
1289
2250
|
preset: "guided-intake"
|
|
1290
2251
|
});
|
|
1291
2252
|
const generalVoice = createVoiceController(generalPath, {
|
|
1292
2253
|
capture: {
|
|
2254
|
+
onAudio: (audio, sendAudio) => {
|
|
2255
|
+
if (generalBargeInBinding) {
|
|
2256
|
+
generalBargeInBinding.sendAudio(audio);
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
sendAudio(audio);
|
|
2260
|
+
},
|
|
1293
2261
|
onLevel: (level) => {
|
|
2262
|
+
generalBargeInBinding?.handleLevel(level);
|
|
1294
2263
|
waveLevels = pushVoiceWaveLevel(waveLevels, level);
|
|
1295
2264
|
renderWave();
|
|
1296
2265
|
}
|
|
1297
2266
|
},
|
|
2267
|
+
connection: {
|
|
2268
|
+
reconnectReportPath
|
|
2269
|
+
},
|
|
1298
2270
|
preset: "dictation"
|
|
1299
2271
|
});
|
|
1300
2272
|
const stopGuidedBinding = guidedVoice.bindHTMX({ element: syncElement });
|
|
1301
2273
|
const stopGeneralBinding = generalVoice.bindHTMX({ element: syncElement });
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
2274
|
+
const guidedAudioPlayer = createVoiceAudioPlayer(guidedVoice);
|
|
2275
|
+
const generalAudioPlayer = createVoiceAudioPlayer(generalVoice);
|
|
2276
|
+
guidedBargeInBinding = bindVoiceBargeIn(guidedVoice, guidedAudioPlayer, {
|
|
2277
|
+
interruptThreshold: bargeInSpeechThreshold,
|
|
2278
|
+
monitor: bargeInMonitor ?? undefined
|
|
2279
|
+
});
|
|
2280
|
+
generalBargeInBinding = bindVoiceBargeIn(generalVoice, generalAudioPlayer, {
|
|
2281
|
+
interruptThreshold: bargeInSpeechThreshold,
|
|
2282
|
+
monitor: bargeInMonitor ?? undefined
|
|
2283
|
+
});
|
|
1310
2284
|
const currentVoice = () => activeMode === "general" ? generalVoice : guidedVoice;
|
|
2285
|
+
const currentAudioPlayer = () => activeMode === "general" ? generalAudioPlayer : guidedAudioPlayer;
|
|
1311
2286
|
const renderWave = () => {
|
|
1312
2287
|
const path = createVoiceWavePath(waveLevels);
|
|
1313
2288
|
voiceWaveGlow.setAttribute("d", path);
|
|
@@ -1319,9 +2294,12 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1319
2294
|
const render = () => {
|
|
1320
2295
|
const voice = currentVoice();
|
|
1321
2296
|
const hasStarted = (activeMode ? hasStartedModes[activeMode] : false) || voice.turns.length > 0;
|
|
1322
|
-
const status = voice
|
|
2297
|
+
const { status } = voice;
|
|
1323
2298
|
connectionMetric.textContent = voice.isConnected ? "Connected" : "Waiting";
|
|
1324
2299
|
errorStatus.textContent = micError || voice.error || "None";
|
|
2300
|
+
if (reconnectStatus) {
|
|
2301
|
+
reconnectStatus.textContent = formatReconnectState(voice.reconnect);
|
|
2302
|
+
}
|
|
1325
2303
|
microphoneStatus.textContent = isCapturing ? DEFAULT_MIC_LIVE : DEFAULT_MIC_IDLE;
|
|
1326
2304
|
promptStatus.textContent = resolvePromptMessage({
|
|
1327
2305
|
guidedPrompts,
|
|
@@ -1385,8 +2363,18 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1385
2363
|
render();
|
|
1386
2364
|
}
|
|
1387
2365
|
};
|
|
1388
|
-
guidedVoice.subscribe(
|
|
1389
|
-
|
|
2366
|
+
guidedVoice.subscribe(() => {
|
|
2367
|
+
if (guidedVoice.assistantAudio.length > 0) {
|
|
2368
|
+
guidedAudioPlayer.start().catch(() => {});
|
|
2369
|
+
}
|
|
2370
|
+
render();
|
|
2371
|
+
});
|
|
2372
|
+
generalVoice.subscribe(() => {
|
|
2373
|
+
if (generalVoice.assistantAudio.length > 0) {
|
|
2374
|
+
generalAudioPlayer.start().catch(() => {});
|
|
2375
|
+
}
|
|
2376
|
+
render();
|
|
2377
|
+
});
|
|
1390
2378
|
startGuidedButton.addEventListener("click", () => {
|
|
1391
2379
|
startMode("guided");
|
|
1392
2380
|
});
|
|
@@ -1396,9 +2384,16 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1396
2384
|
stopButton.addEventListener("click", () => {
|
|
1397
2385
|
stopMic();
|
|
1398
2386
|
});
|
|
2387
|
+
root.addEventListener("absolute-voice-simulate-disconnect", () => {
|
|
2388
|
+
currentVoice().simulateDisconnect();
|
|
2389
|
+
});
|
|
1399
2390
|
window.addEventListener("beforeunload", () => {
|
|
1400
2391
|
guidedVoice.stopRecording();
|
|
1401
2392
|
generalVoice.stopRecording();
|
|
2393
|
+
guidedBargeInBinding?.close();
|
|
2394
|
+
generalBargeInBinding?.close();
|
|
2395
|
+
guidedAudioPlayer.close();
|
|
2396
|
+
generalAudioPlayer.close();
|
|
1402
2397
|
stopGuidedBinding();
|
|
1403
2398
|
stopGeneralBinding();
|
|
1404
2399
|
guidedVoice.close();
|