@absolutejs/voice 0.0.22-beta.24 → 0.0.22-beta.241
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3112 -236
- package/dist/agent.d.ts +62 -0
- package/dist/agentSquadContract.d.ts +69 -0
- package/dist/angular/index.d.ts +14 -0
- package/dist/angular/index.js +3266 -1097
- package/dist/angular/voice-agent-squad-status.service.d.ts +12 -0
- package/dist/angular/voice-campaign-dialer-proof.service.d.ts +14 -0
- package/dist/angular/voice-controller.service.d.ts +1 -0
- package/dist/angular/voice-delivery-runtime.component.d.ts +17 -0
- package/dist/angular/voice-delivery-runtime.service.d.ts +16 -0
- package/dist/angular/voice-live-ops.service.d.ts +11 -0
- package/dist/angular/voice-ops-action-center.service.d.ts +13 -0
- package/dist/angular/voice-ops-status.component.d.ts +15 -0
- package/dist/angular/voice-ops-status.service.d.ts +12 -0
- package/dist/angular/voice-platform-coverage.service.d.ts +12 -0
- package/dist/angular/voice-provider-capabilities.service.d.ts +12 -0
- package/dist/angular/voice-provider-contracts.service.d.ts +12 -0
- package/dist/angular/voice-routing-status.service.d.ts +11 -0
- package/dist/angular/voice-stream.service.d.ts +3 -0
- 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-workflow-status.service.d.ts +12 -0
- package/dist/audit.d.ts +128 -0
- package/dist/auditDeliveryRoutes.d.ts +85 -0
- package/dist/auditExport.d.ts +34 -0
- package/dist/auditRoutes.d.ts +66 -0
- package/dist/auditSinks.d.ts +151 -0
- package/dist/bargeInRoutes.d.ts +56 -0
- package/dist/campaign.d.ts +746 -0
- package/dist/campaignDialers.d.ts +90 -0
- package/dist/client/actions.d.ts +105 -0
- package/dist/client/agentSquadStatus.d.ts +37 -0
- package/dist/client/agentSquadStatusWidget.d.ts +24 -0
- package/dist/client/bargeInMonitor.d.ts +7 -0
- package/dist/client/campaignDialerProof.d.ts +23 -0
- package/dist/client/connection.d.ts +3 -0
- package/dist/client/deliveryRuntime.d.ts +34 -0
- package/dist/client/deliveryRuntimeWidget.d.ts +37 -0
- package/dist/client/duplex.d.ts +1 -1
- package/dist/client/htmxBootstrap.js +747 -15
- package/dist/client/index.d.ts +64 -0
- package/dist/client/index.js +4594 -21
- 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/opsActionCenter.d.ts +54 -0
- package/dist/client/opsActionCenterWidget.d.ts +29 -0
- package/dist/client/opsActionHistory.d.ts +19 -0
- package/dist/client/opsActionHistoryWidget.d.ts +11 -0
- package/dist/client/opsStatus.d.ts +19 -0
- package/dist/client/opsStatusWidget.d.ts +40 -0
- package/dist/client/platformCoverage.d.ts +19 -0
- package/dist/client/platformCoverageWidget.d.ts +37 -0
- package/dist/client/providerCapabilities.d.ts +19 -0
- package/dist/client/providerCapabilitiesWidget.d.ts +32 -0
- package/dist/client/providerContracts.d.ts +19 -0
- package/dist/client/providerContractsWidget.d.ts +37 -0
- package/dist/client/providerSimulationControls.d.ts +33 -0
- package/dist/client/providerSimulationControlsWidget.d.ts +20 -0
- package/dist/client/providerStatusWidget.d.ts +32 -0
- package/dist/client/routingStatus.d.ts +19 -0
- package/dist/client/routingStatusWidget.d.ts +28 -0
- package/dist/client/traceTimeline.d.ts +19 -0
- package/dist/client/traceTimelineWidget.d.ts +36 -0
- package/dist/client/turnLatency.d.ts +22 -0
- package/dist/client/turnLatencyWidget.d.ts +33 -0
- package/dist/client/turnQuality.d.ts +19 -0
- package/dist/client/turnQualityWidget.d.ts +32 -0
- package/dist/client/workflowStatus.d.ts +19 -0
- package/dist/dataControl.d.ts +140 -0
- package/dist/deliveryRuntime.d.ts +158 -0
- package/dist/deliverySinkRoutes.d.ts +117 -0
- package/dist/demoReadyRoutes.d.ts +98 -0
- package/dist/diagnosticsRoutes.d.ts +44 -0
- package/dist/evalRoutes.d.ts +219 -0
- package/dist/fileStore.d.ts +14 -2
- package/dist/handoff.d.ts +54 -0
- package/dist/handoffHealth.d.ts +94 -0
- package/dist/incidentBundle.d.ts +116 -0
- package/dist/index.d.ts +130 -13
- package/dist/index.js +23751 -5201
- package/dist/latencySlo.d.ts +56 -0
- package/dist/liveLatency.d.ts +78 -0
- package/dist/liveOps.d.ts +122 -0
- package/dist/modelAdapters.d.ts +23 -2
- package/dist/observabilityExport.d.ts +428 -0
- package/dist/openaiRealtime.d.ts +27 -0
- package/dist/openaiTTS.d.ts +18 -0
- package/dist/operationsRecord.d.ts +158 -0
- package/dist/opsActionAuditRoutes.d.ts +99 -0
- package/dist/opsConsoleRoutes.d.ts +80 -0
- package/dist/opsRecovery.d.ts +137 -0
- package/dist/opsStatus.d.ts +76 -0
- package/dist/opsStatusRoutes.d.ts +33 -0
- package/dist/opsWebhook.d.ts +126 -0
- package/dist/outcomeContract.d.ts +115 -0
- package/dist/phoneAgent.d.ts +76 -0
- package/dist/phoneAgentProductionSmoke.d.ts +115 -0
- package/dist/platformCoverage.d.ts +73 -0
- package/dist/postgresStore.d.ts +13 -2
- package/dist/productionReadiness.d.ts +466 -0
- package/dist/proofTrends.d.ts +64 -0
- package/dist/providerAdapters.d.ts +48 -0
- package/dist/providerCapabilities.d.ts +92 -0
- package/dist/providerHealth.d.ts +1 -0
- package/dist/providerRoutingContract.d.ts +38 -0
- package/dist/providerSlo.d.ts +114 -0
- package/dist/providerStackRecommendations.d.ts +145 -0
- package/dist/qualityRoutes.d.ts +76 -0
- package/dist/queue.d.ts +61 -0
- package/dist/react/VoiceAgentSquadStatus.d.ts +5 -0
- package/dist/react/VoiceDeliveryRuntime.d.ts +7 -0
- package/dist/react/VoiceOpsActionCenter.d.ts +5 -0
- package/dist/react/VoiceOpsStatus.d.ts +6 -0
- package/dist/react/VoicePlatformCoverage.d.ts +6 -0
- package/dist/react/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/VoiceRoutingStatus.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/index.d.ts +28 -0
- package/dist/react/index.js +4359 -33
- package/dist/react/useVoiceAgentSquadStatus.d.ts +8 -0
- package/dist/react/useVoiceCampaignDialerProof.d.ts +10 -0
- package/dist/react/useVoiceController.d.ts +3 -0
- 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/useVoiceProviderCapabilities.d.ts +8 -0
- package/dist/react/useVoiceProviderContracts.d.ts +8 -0
- package/dist/react/useVoiceProviderSimulationControls.d.ts +10 -0
- package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
- package/dist/react/useVoiceStream.d.ts +3 -0
- 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 +8 -0
- package/dist/readinessProfiles.d.ts +37 -0
- package/dist/reconnectContract.d.ts +87 -0
- package/dist/resilienceRoutes.d.ts +143 -0
- package/dist/sessionReplay.d.ts +12 -0
- package/dist/simulationSuite.d.ts +121 -0
- package/dist/sqliteStore.d.ts +13 -2
- package/dist/svelte/createVoiceAgentSquadStatus.d.ts +9 -0
- package/dist/svelte/createVoiceCampaignDialerProof.d.ts +9 -0
- package/dist/svelte/createVoiceDeliveryRuntime.d.ts +11 -0
- package/dist/svelte/createVoiceLiveOps.d.ts +13 -0
- package/dist/svelte/createVoiceOpsActionCenter.d.ts +10 -0
- package/dist/svelte/createVoiceOpsStatus.d.ts +9 -0
- package/dist/svelte/createVoicePlatformCoverage.d.ts +7 -0
- package/dist/svelte/createVoiceProviderCapabilities.d.ts +10 -0
- package/dist/svelte/createVoiceProviderContracts.d.ts +10 -0
- package/dist/svelte/createVoiceProviderSimulationControls.d.ts +11 -0
- package/dist/svelte/createVoiceProviderStatus.d.ts +4 -2
- package/dist/svelte/createVoiceRoutingStatus.d.ts +10 -0
- 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/createVoiceWorkflowStatus.d.ts +8 -0
- package/dist/svelte/index.d.ts +15 -0
- package/dist/svelte/index.js +4664 -439
- package/dist/telephony/contract.d.ts +61 -0
- package/dist/telephony/matrix.d.ts +97 -0
- package/dist/telephony/plivo.d.ts +254 -0
- package/dist/telephony/telnyx.d.ts +247 -0
- package/dist/telephony/twilio.d.ts +135 -2
- package/dist/telephonyOutcome.d.ts +201 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +2024 -69
- package/dist/testing/ioProviderSimulator.d.ts +41 -0
- package/dist/toolContract.d.ts +133 -0
- package/dist/toolRuntime.d.ts +50 -0
- package/dist/trace.d.ts +19 -1
- package/dist/traceDeliveryRoutes.d.ts +86 -0
- package/dist/traceTimeline.d.ts +97 -0
- package/dist/turnLatency.d.ts +95 -0
- package/dist/turnQuality.d.ts +94 -0
- package/dist/types.d.ts +180 -4
- package/dist/vue/VoiceDeliveryRuntime.d.ts +30 -0
- package/dist/vue/VoiceOpsActionCenter.d.ts +13 -0
- package/dist/vue/VoiceOpsStatus.d.ts +30 -0
- package/dist/vue/VoicePlatformCoverage.d.ts +23 -0
- package/dist/vue/VoiceProviderCapabilities.d.ts +51 -0
- package/dist/vue/VoiceProviderContracts.d.ts +21 -0
- package/dist/vue/VoiceProviderSimulationControls.d.ts +88 -0
- package/dist/vue/VoiceProviderStatus.d.ts +51 -0
- package/dist/vue/VoiceRoutingStatus.d.ts +51 -0
- package/dist/vue/VoiceTurnLatency.d.ts +69 -0
- package/dist/vue/VoiceTurnQuality.d.ts +51 -0
- package/dist/vue/index.d.ts +26 -0
- package/dist/vue/index.js +4136 -57
- package/dist/vue/useVoiceAgentSquadStatus.d.ts +9 -0
- package/dist/vue/useVoiceCampaignDialerProof.d.ts +11 -0
- package/dist/vue/useVoiceController.d.ts +2 -1
- 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/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 +1 -1
- package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
- package/dist/vue/useVoiceStream.d.ts +4 -1
- 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 +9 -0
- package/dist/workflowContract.d.ts +91 -0
- package/package.json +1 -1
package/dist/angular/index.js
CHANGED
|
@@ -69,1245 +69,3398 @@ var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
|
69
69
|
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
// src/angular/voice-
|
|
72
|
+
// src/angular/voice-ops-status.service.ts
|
|
73
73
|
import { computed, Injectable, signal } from "@angular/core";
|
|
74
74
|
|
|
75
|
-
// src/client/
|
|
76
|
-
var
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return value.message;
|
|
75
|
+
// src/client/opsStatus.ts
|
|
76
|
+
var fetchVoiceOpsStatus = async (path = "/api/voice/ops-status", options = {}) => {
|
|
77
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
78
|
+
const response = await fetchImpl(path);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new Error(`Voice ops status failed: HTTP ${response.status}`);
|
|
82
81
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
return await response.json();
|
|
83
|
+
};
|
|
84
|
+
var createVoiceOpsStatusStore = (path = "/api/voice/ops-status", options = {}) => {
|
|
85
|
+
const listeners = new Set;
|
|
86
|
+
let closed = false;
|
|
87
|
+
let timer;
|
|
88
|
+
let snapshot = {
|
|
89
|
+
error: null,
|
|
90
|
+
isLoading: false
|
|
91
|
+
};
|
|
92
|
+
const emit = () => {
|
|
93
|
+
for (const listener of listeners) {
|
|
94
|
+
listener();
|
|
93
95
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
};
|
|
97
|
+
const refresh = async () => {
|
|
98
|
+
if (closed) {
|
|
99
|
+
return snapshot.report;
|
|
96
100
|
}
|
|
101
|
+
snapshot = {
|
|
102
|
+
...snapshot,
|
|
103
|
+
error: null,
|
|
104
|
+
isLoading: true
|
|
105
|
+
};
|
|
106
|
+
emit();
|
|
97
107
|
try {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
switch (message.type) {
|
|
105
|
-
case "audio":
|
|
106
|
-
return {
|
|
107
|
-
chunk: Uint8Array.from(atob(message.chunkBase64), (char) => char.charCodeAt(0)),
|
|
108
|
-
format: message.format,
|
|
109
|
-
receivedAt: message.receivedAt,
|
|
110
|
-
turnId: message.turnId,
|
|
111
|
-
type: "audio"
|
|
112
|
-
};
|
|
113
|
-
case "assistant":
|
|
114
|
-
return {
|
|
115
|
-
text: message.text,
|
|
116
|
-
type: "assistant"
|
|
117
|
-
};
|
|
118
|
-
case "complete":
|
|
119
|
-
return {
|
|
120
|
-
sessionId: message.sessionId,
|
|
121
|
-
type: "complete"
|
|
122
|
-
};
|
|
123
|
-
case "error":
|
|
124
|
-
return {
|
|
125
|
-
message: normalizeErrorMessage(message.message),
|
|
126
|
-
type: "error"
|
|
127
|
-
};
|
|
128
|
-
case "final":
|
|
129
|
-
return {
|
|
130
|
-
transcript: message.transcript,
|
|
131
|
-
type: "final"
|
|
132
|
-
};
|
|
133
|
-
case "partial":
|
|
134
|
-
return {
|
|
135
|
-
transcript: message.transcript,
|
|
136
|
-
type: "partial"
|
|
137
|
-
};
|
|
138
|
-
case "session":
|
|
139
|
-
return {
|
|
140
|
-
sessionId: message.sessionId,
|
|
141
|
-
scenarioId: message.scenarioId,
|
|
142
|
-
status: message.status,
|
|
143
|
-
type: "session"
|
|
108
|
+
const report = await fetchVoiceOpsStatus(path, options);
|
|
109
|
+
snapshot = {
|
|
110
|
+
error: null,
|
|
111
|
+
isLoading: false,
|
|
112
|
+
report,
|
|
113
|
+
updatedAt: Date.now()
|
|
144
114
|
};
|
|
145
|
-
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
|
|
115
|
+
emit();
|
|
116
|
+
return report;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
snapshot = {
|
|
119
|
+
...snapshot,
|
|
120
|
+
error: error instanceof Error ? error.message : String(error),
|
|
121
|
+
isLoading: false
|
|
149
122
|
};
|
|
150
|
-
|
|
151
|
-
|
|
123
|
+
emit();
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const close = () => {
|
|
128
|
+
closed = true;
|
|
129
|
+
if (timer) {
|
|
130
|
+
clearInterval(timer);
|
|
131
|
+
timer = undefined;
|
|
132
|
+
}
|
|
133
|
+
listeners.clear();
|
|
134
|
+
};
|
|
135
|
+
if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
|
|
136
|
+
timer = setInterval(() => {
|
|
137
|
+
refresh().catch(() => {});
|
|
138
|
+
}, options.intervalMs);
|
|
152
139
|
}
|
|
140
|
+
return {
|
|
141
|
+
close,
|
|
142
|
+
getServerSnapshot: () => snapshot,
|
|
143
|
+
getSnapshot: () => snapshot,
|
|
144
|
+
refresh,
|
|
145
|
+
subscribe: (listener) => {
|
|
146
|
+
listeners.add(listener);
|
|
147
|
+
return () => {
|
|
148
|
+
listeners.delete(listener);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
};
|
|
153
152
|
};
|
|
154
153
|
|
|
155
|
-
// src/
|
|
156
|
-
var
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
var
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
switch (value.type) {
|
|
193
|
-
case "audio":
|
|
194
|
-
case "assistant":
|
|
195
|
-
case "complete":
|
|
196
|
-
case "error":
|
|
197
|
-
case "final":
|
|
198
|
-
case "partial":
|
|
199
|
-
case "pong":
|
|
200
|
-
case "session":
|
|
201
|
-
case "turn":
|
|
202
|
-
return true;
|
|
203
|
-
default:
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
var parseServerMessage = (event) => {
|
|
208
|
-
if (typeof event.data !== "string") {
|
|
209
|
-
return null;
|
|
154
|
+
// src/angular/voice-ops-status.service.ts
|
|
155
|
+
var _dec = [
|
|
156
|
+
Injectable({ providedIn: "root" })
|
|
157
|
+
];
|
|
158
|
+
var _init = __decoratorStart(undefined);
|
|
159
|
+
|
|
160
|
+
class VoiceOpsStatusService {
|
|
161
|
+
connect(path = "/api/voice/ops-status", options = {}) {
|
|
162
|
+
const store = createVoiceOpsStatusStore(path, options);
|
|
163
|
+
const errorSignal = signal(null);
|
|
164
|
+
const isLoadingSignal = signal(false);
|
|
165
|
+
const reportSignal = signal(undefined);
|
|
166
|
+
const updatedAtSignal = signal(undefined);
|
|
167
|
+
const sync = () => {
|
|
168
|
+
const snapshot = store.getSnapshot();
|
|
169
|
+
errorSignal.set(snapshot.error);
|
|
170
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
171
|
+
reportSignal.set(snapshot.report);
|
|
172
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
173
|
+
};
|
|
174
|
+
const unsubscribe = store.subscribe(sync);
|
|
175
|
+
sync();
|
|
176
|
+
if (typeof window !== "undefined") {
|
|
177
|
+
store.refresh().catch(() => {});
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
close: () => {
|
|
181
|
+
unsubscribe();
|
|
182
|
+
store.close();
|
|
183
|
+
},
|
|
184
|
+
error: computed(() => errorSignal()),
|
|
185
|
+
isLoading: computed(() => isLoadingSignal()),
|
|
186
|
+
refresh: store.refresh,
|
|
187
|
+
report: computed(() => reportSignal()),
|
|
188
|
+
updatedAt: computed(() => updatedAtSignal())
|
|
189
|
+
};
|
|
210
190
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
191
|
+
}
|
|
192
|
+
VoiceOpsStatusService = __decorateElement(_init, 0, "VoiceOpsStatusService", _dec, VoiceOpsStatusService);
|
|
193
|
+
__runInitializers(_init, 1, VoiceOpsStatusService);
|
|
194
|
+
__decoratorMetadata(_init, VoiceOpsStatusService);
|
|
195
|
+
let _VoiceOpsStatusService = VoiceOpsStatusService;
|
|
196
|
+
// src/angular/voice-platform-coverage.service.ts
|
|
197
|
+
import { computed as computed2, Injectable as Injectable2, signal as signal2 } from "@angular/core";
|
|
198
|
+
|
|
199
|
+
// src/client/platformCoverage.ts
|
|
200
|
+
var fetchVoicePlatformCoverage = async (path = "/api/voice/platform-coverage", options = {}) => {
|
|
201
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
202
|
+
const response = await fetchImpl(path);
|
|
203
|
+
if (!response.ok) {
|
|
204
|
+
throw new Error(`Voice platform coverage failed: HTTP ${response.status}`);
|
|
216
205
|
}
|
|
206
|
+
return await response.json();
|
|
217
207
|
};
|
|
218
|
-
var
|
|
219
|
-
if (typeof window === "undefined") {
|
|
220
|
-
return NOOP_CONNECTION;
|
|
221
|
-
}
|
|
208
|
+
var createVoicePlatformCoverageStore = (path = "/api/voice/platform-coverage", options = {}) => {
|
|
222
209
|
const listeners = new Set;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
pendingMessages: [],
|
|
229
|
-
scenarioId: options.scenarioId ?? null,
|
|
230
|
-
pingInterval: null,
|
|
231
|
-
reconnectAttempts: 0,
|
|
232
|
-
reconnectTimeout: null,
|
|
233
|
-
sessionId: options.sessionId ?? createSessionId(),
|
|
234
|
-
ws: null
|
|
210
|
+
let closed = false;
|
|
211
|
+
let timer;
|
|
212
|
+
let snapshot = {
|
|
213
|
+
error: null,
|
|
214
|
+
isLoading: false
|
|
235
215
|
};
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
state.pingInterval = null;
|
|
240
|
-
}
|
|
241
|
-
if (state.reconnectTimeout) {
|
|
242
|
-
clearTimeout(state.reconnectTimeout);
|
|
243
|
-
state.reconnectTimeout = null;
|
|
216
|
+
const emit = () => {
|
|
217
|
+
for (const listener of listeners) {
|
|
218
|
+
listener();
|
|
244
219
|
}
|
|
245
220
|
};
|
|
246
|
-
const
|
|
247
|
-
if (
|
|
248
|
-
return;
|
|
221
|
+
const refresh = async () => {
|
|
222
|
+
if (closed) {
|
|
223
|
+
return snapshot.report;
|
|
249
224
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
225
|
+
snapshot = {
|
|
226
|
+
...snapshot,
|
|
227
|
+
error: null,
|
|
228
|
+
isLoading: true
|
|
229
|
+
};
|
|
230
|
+
emit();
|
|
231
|
+
try {
|
|
232
|
+
const report = await fetchVoicePlatformCoverage(path, options);
|
|
233
|
+
snapshot = {
|
|
234
|
+
error: null,
|
|
235
|
+
isLoading: false,
|
|
236
|
+
report,
|
|
237
|
+
updatedAt: Date.now()
|
|
238
|
+
};
|
|
239
|
+
emit();
|
|
240
|
+
return report;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
snapshot = {
|
|
243
|
+
...snapshot,
|
|
244
|
+
error: error instanceof Error ? error.message : String(error),
|
|
245
|
+
isLoading: false
|
|
246
|
+
};
|
|
247
|
+
emit();
|
|
248
|
+
throw error;
|
|
255
249
|
}
|
|
256
250
|
};
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}, RECONNECT_DELAY_MS);
|
|
251
|
+
const close = () => {
|
|
252
|
+
closed = true;
|
|
253
|
+
if (timer) {
|
|
254
|
+
clearInterval(timer);
|
|
255
|
+
timer = undefined;
|
|
256
|
+
}
|
|
257
|
+
listeners.clear();
|
|
265
258
|
};
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
259
|
+
if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
|
|
260
|
+
timer = setInterval(() => {
|
|
261
|
+
refresh().catch(() => {});
|
|
262
|
+
}, options.intervalMs);
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
close,
|
|
266
|
+
getServerSnapshot: () => snapshot,
|
|
267
|
+
getSnapshot: () => snapshot,
|
|
268
|
+
refresh,
|
|
269
|
+
subscribe: (listener) => {
|
|
270
|
+
listeners.add(listener);
|
|
271
|
+
return () => {
|
|
272
|
+
listeners.delete(listener);
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// src/angular/voice-platform-coverage.service.ts
|
|
279
|
+
var _dec = [
|
|
280
|
+
Injectable2({ providedIn: "root" })
|
|
281
|
+
];
|
|
282
|
+
var _init = __decoratorStart(undefined);
|
|
283
|
+
|
|
284
|
+
class VoicePlatformCoverageService {
|
|
285
|
+
connect(path = "/api/voice/platform-coverage", options = {}) {
|
|
286
|
+
const store = createVoicePlatformCoverageStore(path, options);
|
|
287
|
+
const errorSignal = signal2(null);
|
|
288
|
+
const isLoadingSignal = signal2(false);
|
|
289
|
+
const reportSignal = signal2(undefined);
|
|
290
|
+
const updatedAtSignal = signal2(undefined);
|
|
291
|
+
const sync = () => {
|
|
292
|
+
const snapshot = store.getSnapshot();
|
|
293
|
+
errorSignal.set(snapshot.error);
|
|
294
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
295
|
+
reportSignal.set(snapshot.report);
|
|
296
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
295
297
|
};
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
298
|
+
const unsubscribe = store.subscribe(sync);
|
|
299
|
+
sync();
|
|
300
|
+
if (typeof window !== "undefined") {
|
|
301
|
+
store.refresh().catch(() => {});
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
close: () => {
|
|
305
|
+
unsubscribe();
|
|
306
|
+
store.close();
|
|
307
|
+
},
|
|
308
|
+
error: computed2(() => errorSignal()),
|
|
309
|
+
isLoading: computed2(() => isLoadingSignal()),
|
|
310
|
+
refresh: store.refresh,
|
|
311
|
+
report: computed2(() => reportSignal()),
|
|
312
|
+
updatedAt: computed2(() => updatedAtSignal())
|
|
303
313
|
};
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
VoicePlatformCoverageService = __decorateElement(_init, 0, "VoicePlatformCoverageService", _dec, VoicePlatformCoverageService);
|
|
317
|
+
__runInitializers(_init, 1, VoicePlatformCoverageService);
|
|
318
|
+
__decoratorMetadata(_init, VoicePlatformCoverageService);
|
|
319
|
+
let _VoicePlatformCoverageService = VoicePlatformCoverageService;
|
|
320
|
+
// src/angular/voice-ops-action-center.service.ts
|
|
321
|
+
import { computed as computed3, Injectable as Injectable3, signal as signal3 } from "@angular/core";
|
|
322
|
+
|
|
323
|
+
// src/client/opsActionCenter.ts
|
|
324
|
+
var recordVoiceOpsActionResult = async (result, options = {}) => {
|
|
325
|
+
if (options.auditPath === false) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const path = options.auditPath ?? "/api/voice/ops-actions/audit";
|
|
329
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
330
|
+
const response = await fetchImpl(path, {
|
|
331
|
+
body: JSON.stringify(result),
|
|
332
|
+
headers: {
|
|
333
|
+
"Content-Type": "application/json"
|
|
334
|
+
},
|
|
335
|
+
method: "POST"
|
|
336
|
+
});
|
|
337
|
+
if (!response.ok) {
|
|
338
|
+
throw new Error(`Voice ops action audit failed: HTTP ${response.status}`);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
var createVoiceOpsActionCenterActions = (options = {}) => {
|
|
342
|
+
const deliveryRuntimePath = options.deliveryRuntimePath ?? "/api/voice-delivery-runtime";
|
|
343
|
+
const actions = [];
|
|
344
|
+
if (options.includeProductionReadiness !== false) {
|
|
345
|
+
actions.push({
|
|
346
|
+
description: "Refresh the production readiness report.",
|
|
347
|
+
id: "production-readiness",
|
|
348
|
+
label: "Refresh readiness",
|
|
349
|
+
method: "GET",
|
|
350
|
+
path: options.productionReadinessPath ?? "/api/production-readiness"
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
if (options.includeDeliveryRuntime !== false) {
|
|
354
|
+
actions.push({
|
|
355
|
+
description: "Drain pending and failed audit/trace deliveries.",
|
|
356
|
+
id: "delivery-runtime.tick",
|
|
357
|
+
label: "Tick delivery workers",
|
|
358
|
+
method: "POST",
|
|
359
|
+
path: `${deliveryRuntimePath.replace(/\/$/, "")}/tick`
|
|
360
|
+
}, {
|
|
361
|
+
description: "Move reviewed dead letters back to live delivery queues.",
|
|
362
|
+
id: "delivery-runtime.requeue-dead-letters",
|
|
363
|
+
label: "Requeue dead letters",
|
|
364
|
+
method: "POST",
|
|
365
|
+
path: `${deliveryRuntimePath.replace(/\/$/, "")}/requeue-dead-letters`
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
if (options.includeTurnLatencyProof !== false) {
|
|
369
|
+
actions.push({
|
|
370
|
+
description: "Run the synthetic turn latency proof.",
|
|
371
|
+
id: "turn-latency.proof",
|
|
372
|
+
label: "Run latency proof",
|
|
373
|
+
method: "POST",
|
|
374
|
+
path: options.turnLatencyProofPath ?? "/api/turn-latency/proof"
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
if (options.includeProviderSimulation !== false) {
|
|
378
|
+
const pathPrefix = options.providerSimulationPathPrefix ?? "/api/stt-simulate";
|
|
379
|
+
for (const provider of options.providers ?? []) {
|
|
380
|
+
actions.push({
|
|
381
|
+
description: `Simulate ${provider} provider failure.`,
|
|
382
|
+
id: `provider.${provider}.failure`,
|
|
383
|
+
label: `Simulate ${provider} failure`,
|
|
384
|
+
method: "POST",
|
|
385
|
+
path: `${pathPrefix}/failure?provider=${encodeURIComponent(provider)}`
|
|
386
|
+
}, {
|
|
387
|
+
description: `Mark ${provider} provider recovered.`,
|
|
388
|
+
id: `provider.${provider}.recovery`,
|
|
389
|
+
label: `Recover ${provider}`,
|
|
390
|
+
method: "POST",
|
|
391
|
+
path: `${pathPrefix}/recovery?provider=${encodeURIComponent(provider)}`
|
|
392
|
+
});
|
|
310
393
|
}
|
|
311
|
-
|
|
394
|
+
}
|
|
395
|
+
return actions;
|
|
396
|
+
};
|
|
397
|
+
var runVoiceOpsAction = async (action, options = {}) => {
|
|
398
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
399
|
+
const response = await fetchImpl(action.path, {
|
|
400
|
+
method: action.method ?? "POST"
|
|
401
|
+
});
|
|
402
|
+
const body = await response.json().catch(() => null);
|
|
403
|
+
if (!response.ok) {
|
|
404
|
+
const message = body && typeof body === "object" && "error" in body ? String(body.error) : `Voice ops action "${action.id}" failed: HTTP ${response.status}`;
|
|
405
|
+
throw new Error(message);
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
actionId: action.id,
|
|
409
|
+
body,
|
|
410
|
+
ok: response.ok,
|
|
411
|
+
ranAt: Date.now(),
|
|
412
|
+
status: response.status
|
|
312
413
|
};
|
|
313
|
-
|
|
314
|
-
|
|
414
|
+
};
|
|
415
|
+
var createVoiceOpsActionCenterStore = (options = {}) => {
|
|
416
|
+
const listeners = new Set;
|
|
417
|
+
let closed = false;
|
|
418
|
+
let timer;
|
|
419
|
+
let snapshot = {
|
|
420
|
+
actions: options.actions ?? createVoiceOpsActionCenterActions(),
|
|
421
|
+
error: null,
|
|
422
|
+
isRunning: false
|
|
315
423
|
};
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
if (input.scenarioId) {
|
|
321
|
-
state.scenarioId = input.scenarioId;
|
|
424
|
+
const emit = () => {
|
|
425
|
+
for (const listener of listeners) {
|
|
426
|
+
listener();
|
|
322
427
|
}
|
|
323
|
-
send({
|
|
324
|
-
type: "start",
|
|
325
|
-
sessionId: state.sessionId,
|
|
326
|
-
scenarioId: state.scenarioId ?? undefined
|
|
327
|
-
});
|
|
328
428
|
};
|
|
329
|
-
const
|
|
330
|
-
|
|
429
|
+
const setActions = (actions) => {
|
|
430
|
+
snapshot = { ...snapshot, actions, updatedAt: Date.now() };
|
|
431
|
+
emit();
|
|
331
432
|
};
|
|
332
|
-
const
|
|
333
|
-
|
|
433
|
+
const run = async (actionId) => {
|
|
434
|
+
if (closed) {
|
|
435
|
+
return snapshot.lastResult;
|
|
436
|
+
}
|
|
437
|
+
const action = snapshot.actions.find((item) => item.id === actionId);
|
|
438
|
+
if (!action) {
|
|
439
|
+
throw new Error(`Voice ops action "${actionId}" is not configured.`);
|
|
440
|
+
}
|
|
441
|
+
if (action.disabled) {
|
|
442
|
+
throw new Error(`Voice ops action "${actionId}" is disabled.`);
|
|
443
|
+
}
|
|
444
|
+
snapshot = {
|
|
445
|
+
...snapshot,
|
|
446
|
+
error: null,
|
|
447
|
+
isRunning: true,
|
|
448
|
+
runningActionId: action.id
|
|
449
|
+
};
|
|
450
|
+
emit();
|
|
451
|
+
try {
|
|
452
|
+
const result = await runVoiceOpsAction(action, options);
|
|
453
|
+
await options.onActionResult?.(result);
|
|
454
|
+
await recordVoiceOpsActionResult(result, options);
|
|
455
|
+
snapshot = {
|
|
456
|
+
...snapshot,
|
|
457
|
+
error: null,
|
|
458
|
+
isRunning: false,
|
|
459
|
+
lastResult: result,
|
|
460
|
+
runningActionId: undefined,
|
|
461
|
+
updatedAt: Date.now()
|
|
462
|
+
};
|
|
463
|
+
emit();
|
|
464
|
+
return result;
|
|
465
|
+
} catch (error) {
|
|
466
|
+
const result = {
|
|
467
|
+
actionId: action.id,
|
|
468
|
+
body: null,
|
|
469
|
+
error: error instanceof Error ? error.message : String(error),
|
|
470
|
+
ok: false,
|
|
471
|
+
ranAt: Date.now(),
|
|
472
|
+
status: 0
|
|
473
|
+
};
|
|
474
|
+
await options.onActionResult?.(result);
|
|
475
|
+
await recordVoiceOpsActionResult(result, options).catch(() => {});
|
|
476
|
+
snapshot = {
|
|
477
|
+
...snapshot,
|
|
478
|
+
error: error instanceof Error ? error.message : String(error),
|
|
479
|
+
isRunning: false,
|
|
480
|
+
runningActionId: undefined
|
|
481
|
+
};
|
|
482
|
+
emit();
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
334
485
|
};
|
|
335
486
|
const close = () => {
|
|
336
|
-
|
|
337
|
-
if (
|
|
338
|
-
|
|
339
|
-
|
|
487
|
+
closed = true;
|
|
488
|
+
if (timer) {
|
|
489
|
+
clearInterval(timer);
|
|
490
|
+
timer = undefined;
|
|
340
491
|
}
|
|
341
|
-
state.isConnected = false;
|
|
342
492
|
listeners.clear();
|
|
343
493
|
};
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
};
|
|
350
|
-
connect();
|
|
494
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
495
|
+
timer = setInterval(() => {
|
|
496
|
+
emit();
|
|
497
|
+
}, options.intervalMs);
|
|
498
|
+
}
|
|
351
499
|
return {
|
|
352
|
-
start,
|
|
353
500
|
close,
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
501
|
+
getServerSnapshot: () => snapshot,
|
|
502
|
+
getSnapshot: () => snapshot,
|
|
503
|
+
run,
|
|
504
|
+
setActions,
|
|
505
|
+
subscribe: (listener) => {
|
|
506
|
+
listeners.add(listener);
|
|
507
|
+
return () => {
|
|
508
|
+
listeners.delete(listener);
|
|
509
|
+
};
|
|
510
|
+
}
|
|
361
511
|
};
|
|
362
512
|
};
|
|
363
513
|
|
|
364
|
-
// src/
|
|
365
|
-
var
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
514
|
+
// src/angular/voice-ops-action-center.service.ts
|
|
515
|
+
var _dec = [
|
|
516
|
+
Injectable3({ providedIn: "root" })
|
|
517
|
+
];
|
|
518
|
+
var _init = __decoratorStart(undefined);
|
|
519
|
+
|
|
520
|
+
class VoiceOpsActionCenterService {
|
|
521
|
+
connect(options = {}) {
|
|
522
|
+
const store = createVoiceOpsActionCenterStore(options);
|
|
523
|
+
const actionsSignal = signal3([]);
|
|
524
|
+
const errorSignal = signal3(null);
|
|
525
|
+
const isRunningSignal = signal3(false);
|
|
526
|
+
const lastResultSignal = signal3(undefined);
|
|
527
|
+
const runningActionIdSignal = signal3(undefined);
|
|
528
|
+
const sync = () => {
|
|
529
|
+
const snapshot = store.getSnapshot();
|
|
530
|
+
actionsSignal.set(snapshot.actions);
|
|
531
|
+
errorSignal.set(snapshot.error);
|
|
532
|
+
isRunningSignal.set(snapshot.isRunning);
|
|
533
|
+
lastResultSignal.set(snapshot.lastResult);
|
|
534
|
+
runningActionIdSignal.set(snapshot.runningActionId);
|
|
535
|
+
};
|
|
536
|
+
const unsubscribe = store.subscribe(sync);
|
|
537
|
+
sync();
|
|
538
|
+
return {
|
|
539
|
+
actions: computed3(() => actionsSignal()),
|
|
540
|
+
close: () => {
|
|
541
|
+
unsubscribe();
|
|
542
|
+
store.close();
|
|
543
|
+
},
|
|
544
|
+
error: computed3(() => errorSignal()),
|
|
545
|
+
isRunning: computed3(() => isRunningSignal()),
|
|
546
|
+
lastResult: computed3(() => lastResultSignal()),
|
|
547
|
+
run: store.run,
|
|
548
|
+
runningActionId: computed3(() => runningActionIdSignal()),
|
|
549
|
+
setActions: store.setActions
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
VoiceOpsActionCenterService = __decorateElement(_init, 0, "VoiceOpsActionCenterService", _dec, VoiceOpsActionCenterService);
|
|
554
|
+
__runInitializers(_init, 1, VoiceOpsActionCenterService);
|
|
555
|
+
__decoratorMetadata(_init, VoiceOpsActionCenterService);
|
|
556
|
+
let _VoiceOpsActionCenterService = VoiceOpsActionCenterService;
|
|
557
|
+
// src/angular/voice-live-ops.service.ts
|
|
558
|
+
import { computed as computed4, Injectable as Injectable4, signal as signal4 } from "@angular/core";
|
|
559
|
+
|
|
560
|
+
// src/client/liveOps.ts
|
|
561
|
+
var postVoiceLiveOpsAction = async (input, options = {}) => {
|
|
562
|
+
if (!input.sessionId) {
|
|
563
|
+
throw new Error("Start a voice session before running live ops actions.");
|
|
564
|
+
}
|
|
565
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
566
|
+
const response = await fetchImpl(options.actionPath ?? "/api/voice/live-ops/action", {
|
|
567
|
+
body: JSON.stringify(input),
|
|
568
|
+
headers: {
|
|
569
|
+
"Content-Type": "application/json"
|
|
570
|
+
},
|
|
571
|
+
method: "POST"
|
|
572
|
+
});
|
|
573
|
+
const payload = await response.json().catch(() => null);
|
|
574
|
+
if (!response.ok || !payload?.ok) {
|
|
575
|
+
const message = payload && typeof payload === "object" && "error" in payload ? String(payload.error) : `Voice live ops action failed: HTTP ${response.status}`;
|
|
576
|
+
throw new Error(message);
|
|
577
|
+
}
|
|
578
|
+
return payload;
|
|
579
|
+
};
|
|
580
|
+
var createVoiceLiveOpsStore = (options = {}) => {
|
|
581
|
+
const listeners = new Set;
|
|
582
|
+
let closed = false;
|
|
583
|
+
let snapshot = {
|
|
584
|
+
error: null,
|
|
585
|
+
isRunning: false
|
|
381
586
|
};
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
state = {
|
|
386
|
-
...state,
|
|
387
|
-
assistantAudio: [
|
|
388
|
-
...state.assistantAudio,
|
|
389
|
-
{
|
|
390
|
-
chunk: action.chunk,
|
|
391
|
-
format: action.format,
|
|
392
|
-
receivedAt: action.receivedAt,
|
|
393
|
-
turnId: action.turnId
|
|
394
|
-
}
|
|
395
|
-
]
|
|
396
|
-
};
|
|
397
|
-
break;
|
|
398
|
-
case "assistant":
|
|
399
|
-
state = {
|
|
400
|
-
...state,
|
|
401
|
-
assistantTexts: [...state.assistantTexts, action.text]
|
|
402
|
-
};
|
|
403
|
-
break;
|
|
404
|
-
case "complete":
|
|
405
|
-
state = {
|
|
406
|
-
...state,
|
|
407
|
-
sessionId: action.sessionId,
|
|
408
|
-
status: "completed"
|
|
409
|
-
};
|
|
410
|
-
break;
|
|
411
|
-
case "connected":
|
|
412
|
-
state = {
|
|
413
|
-
...state,
|
|
414
|
-
isConnected: true
|
|
415
|
-
};
|
|
416
|
-
break;
|
|
417
|
-
case "disconnected":
|
|
418
|
-
state = {
|
|
419
|
-
...state,
|
|
420
|
-
isConnected: false
|
|
421
|
-
};
|
|
422
|
-
break;
|
|
423
|
-
case "error":
|
|
424
|
-
state = {
|
|
425
|
-
...state,
|
|
426
|
-
error: action.message
|
|
427
|
-
};
|
|
428
|
-
break;
|
|
429
|
-
case "final":
|
|
430
|
-
state = {
|
|
431
|
-
...state,
|
|
432
|
-
partial: action.transcript.text,
|
|
433
|
-
turns: state.turns.map((turn) => turn)
|
|
434
|
-
};
|
|
435
|
-
break;
|
|
436
|
-
case "partial":
|
|
437
|
-
state = {
|
|
438
|
-
...state,
|
|
439
|
-
partial: action.transcript.text
|
|
440
|
-
};
|
|
441
|
-
break;
|
|
442
|
-
case "session":
|
|
443
|
-
state = {
|
|
444
|
-
...state,
|
|
445
|
-
error: null,
|
|
446
|
-
scenarioId: action.scenarioId ?? state.scenarioId,
|
|
447
|
-
isConnected: action.status === "active",
|
|
448
|
-
sessionId: action.sessionId,
|
|
449
|
-
status: action.status
|
|
450
|
-
};
|
|
451
|
-
break;
|
|
452
|
-
case "turn":
|
|
453
|
-
state = {
|
|
454
|
-
...state,
|
|
455
|
-
partial: "",
|
|
456
|
-
turns: [...state.turns, action.turn]
|
|
457
|
-
};
|
|
458
|
-
break;
|
|
587
|
+
const emit = () => {
|
|
588
|
+
for (const listener of listeners) {
|
|
589
|
+
listener();
|
|
459
590
|
}
|
|
460
|
-
notify();
|
|
461
591
|
};
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
592
|
+
const run = async (input) => {
|
|
593
|
+
if (closed) {
|
|
594
|
+
return snapshot.lastResult;
|
|
595
|
+
}
|
|
596
|
+
snapshot = {
|
|
597
|
+
...snapshot,
|
|
598
|
+
error: null,
|
|
599
|
+
isRunning: true,
|
|
600
|
+
runningAction: input.action
|
|
601
|
+
};
|
|
602
|
+
emit();
|
|
603
|
+
try {
|
|
604
|
+
const result = await postVoiceLiveOpsAction(input, options);
|
|
605
|
+
await options.onControl?.(result);
|
|
606
|
+
snapshot = {
|
|
607
|
+
...snapshot,
|
|
608
|
+
error: null,
|
|
609
|
+
isRunning: false,
|
|
610
|
+
lastResult: result,
|
|
611
|
+
runningAction: undefined,
|
|
612
|
+
updatedAt: Date.now()
|
|
613
|
+
};
|
|
614
|
+
emit();
|
|
615
|
+
return result;
|
|
616
|
+
} catch (error) {
|
|
617
|
+
snapshot = {
|
|
618
|
+
...snapshot,
|
|
619
|
+
error: error instanceof Error ? error.message : String(error),
|
|
620
|
+
isRunning: false,
|
|
621
|
+
runningAction: undefined,
|
|
622
|
+
updatedAt: Date.now()
|
|
470
623
|
};
|
|
624
|
+
emit();
|
|
625
|
+
throw error;
|
|
471
626
|
}
|
|
472
627
|
};
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
var createVoiceStream = (path, options = {}) => {
|
|
477
|
-
const connection = createVoiceConnection(path, options);
|
|
478
|
-
const store = createVoiceStreamStore();
|
|
479
|
-
const subscribers = new Set;
|
|
480
|
-
const start = (input) => Promise.resolve().then(() => {
|
|
481
|
-
if (!input?.sessionId && !input?.scenarioId) {
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
connection.start(input);
|
|
485
|
-
});
|
|
486
|
-
const notify = () => {
|
|
487
|
-
subscribers.forEach((subscriber) => subscriber());
|
|
628
|
+
const close = () => {
|
|
629
|
+
closed = true;
|
|
630
|
+
listeners.clear();
|
|
488
631
|
};
|
|
489
|
-
const unsubscribeConnection = connection.subscribe((message) => {
|
|
490
|
-
const action = serverMessageToAction(message);
|
|
491
|
-
if (action) {
|
|
492
|
-
store.dispatch(action);
|
|
493
|
-
notify();
|
|
494
|
-
}
|
|
495
|
-
});
|
|
496
632
|
return {
|
|
497
|
-
close
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
endTurn() {
|
|
504
|
-
connection.endTurn();
|
|
505
|
-
},
|
|
506
|
-
get error() {
|
|
507
|
-
return store.getSnapshot().error;
|
|
508
|
-
},
|
|
509
|
-
getServerSnapshot() {
|
|
510
|
-
return store.getServerSnapshot();
|
|
511
|
-
},
|
|
512
|
-
getSnapshot() {
|
|
513
|
-
return store.getSnapshot();
|
|
514
|
-
},
|
|
515
|
-
get isConnected() {
|
|
516
|
-
return store.getSnapshot().isConnected;
|
|
517
|
-
},
|
|
518
|
-
get scenarioId() {
|
|
519
|
-
return store.getSnapshot().scenarioId;
|
|
520
|
-
},
|
|
521
|
-
start,
|
|
522
|
-
get partial() {
|
|
523
|
-
return store.getSnapshot().partial;
|
|
524
|
-
},
|
|
525
|
-
get sessionId() {
|
|
526
|
-
return connection.getSessionId();
|
|
527
|
-
},
|
|
528
|
-
get status() {
|
|
529
|
-
return store.getSnapshot().status;
|
|
530
|
-
},
|
|
531
|
-
get turns() {
|
|
532
|
-
return store.getSnapshot().turns;
|
|
533
|
-
},
|
|
534
|
-
get assistantTexts() {
|
|
535
|
-
return store.getSnapshot().assistantTexts;
|
|
536
|
-
},
|
|
537
|
-
get assistantAudio() {
|
|
538
|
-
return store.getSnapshot().assistantAudio;
|
|
539
|
-
},
|
|
540
|
-
sendAudio(audio) {
|
|
541
|
-
connection.sendAudio(audio);
|
|
542
|
-
},
|
|
543
|
-
subscribe(subscriber) {
|
|
544
|
-
subscribers.add(subscriber);
|
|
633
|
+
close,
|
|
634
|
+
getServerSnapshot: () => snapshot,
|
|
635
|
+
getSnapshot: () => snapshot,
|
|
636
|
+
run,
|
|
637
|
+
subscribe: (listener) => {
|
|
638
|
+
listeners.add(listener);
|
|
545
639
|
return () => {
|
|
546
|
-
|
|
640
|
+
listeners.delete(listener);
|
|
547
641
|
};
|
|
548
642
|
}
|
|
549
643
|
};
|
|
550
644
|
};
|
|
551
645
|
|
|
552
|
-
// src/angular/voice-
|
|
646
|
+
// src/angular/voice-live-ops.service.ts
|
|
553
647
|
var _dec = [
|
|
554
|
-
|
|
648
|
+
Injectable4({ providedIn: "root" })
|
|
555
649
|
];
|
|
556
650
|
var _init = __decoratorStart(undefined);
|
|
557
651
|
|
|
558
|
-
class
|
|
559
|
-
connect(
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
const
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
const partialSignal = signal("");
|
|
566
|
-
const sessionIdSignal = signal(stream.sessionId);
|
|
567
|
-
const statusSignal = signal(stream.status);
|
|
568
|
-
const turnsSignal = signal([]);
|
|
652
|
+
class VoiceLiveOpsService {
|
|
653
|
+
connect(options = {}) {
|
|
654
|
+
const store = createVoiceLiveOpsStore(options);
|
|
655
|
+
const errorSignal = signal4(null);
|
|
656
|
+
const isRunningSignal = signal4(false);
|
|
657
|
+
const lastResultSignal = signal4(undefined);
|
|
658
|
+
const runningActionSignal = signal4(undefined);
|
|
569
659
|
const sync = () => {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
sessionIdSignal.set(stream.sessionId);
|
|
576
|
-
statusSignal.set(stream.status);
|
|
577
|
-
turnsSignal.set([...stream.turns]);
|
|
660
|
+
const snapshot = store.getSnapshot();
|
|
661
|
+
errorSignal.set(snapshot.error);
|
|
662
|
+
isRunningSignal.set(snapshot.isRunning);
|
|
663
|
+
lastResultSignal.set(snapshot.lastResult);
|
|
664
|
+
runningActionSignal.set(snapshot.runningAction);
|
|
578
665
|
};
|
|
579
|
-
const unsubscribe =
|
|
666
|
+
const unsubscribe = store.subscribe(sync);
|
|
580
667
|
sync();
|
|
581
668
|
return {
|
|
582
|
-
assistantAudio: computed(() => assistantAudioSignal()),
|
|
583
|
-
assistantTexts: computed(() => assistantTextsSignal()),
|
|
584
669
|
close: () => {
|
|
585
670
|
unsubscribe();
|
|
586
|
-
|
|
671
|
+
store.close();
|
|
587
672
|
},
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
sessionId: computed(() => sessionIdSignal()),
|
|
594
|
-
status: computed(() => statusSignal()),
|
|
595
|
-
turns: computed(() => turnsSignal())
|
|
673
|
+
error: computed4(() => errorSignal()),
|
|
674
|
+
isRunning: computed4(() => isRunningSignal()),
|
|
675
|
+
lastResult: computed4(() => lastResultSignal()),
|
|
676
|
+
run: store.run,
|
|
677
|
+
runningAction: computed4(() => runningActionSignal())
|
|
596
678
|
};
|
|
597
679
|
}
|
|
598
680
|
}
|
|
599
|
-
|
|
600
|
-
__runInitializers(_init, 1,
|
|
601
|
-
__decoratorMetadata(_init,
|
|
602
|
-
let
|
|
603
|
-
// src/angular/voice-
|
|
604
|
-
import { computed as
|
|
681
|
+
VoiceLiveOpsService = __decorateElement(_init, 0, "VoiceLiveOpsService", _dec, VoiceLiveOpsService);
|
|
682
|
+
__runInitializers(_init, 1, VoiceLiveOpsService);
|
|
683
|
+
__decoratorMetadata(_init, VoiceLiveOpsService);
|
|
684
|
+
let _VoiceLiveOpsService = VoiceLiveOpsService;
|
|
685
|
+
// src/angular/voice-delivery-runtime.service.ts
|
|
686
|
+
import { computed as computed5, Injectable as Injectable5, signal as signal5 } from "@angular/core";
|
|
605
687
|
|
|
606
|
-
// src/client/
|
|
607
|
-
var
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
if (typeof input !== "string") {
|
|
611
|
-
return input;
|
|
688
|
+
// src/client/deliveryRuntime.ts
|
|
689
|
+
var getDefaultActionPath = (path, action, options) => {
|
|
690
|
+
if (action === "tick") {
|
|
691
|
+
return options.tickPath ?? `${path.replace(/\/$/, "")}/tick`;
|
|
612
692
|
}
|
|
613
|
-
return
|
|
693
|
+
return options.requeueDeadLettersPath ?? `${path.replace(/\/$/, "")}/requeue-dead-letters`;
|
|
614
694
|
};
|
|
615
|
-
var
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
const url = new URL(baseRoute, window.location.origin);
|
|
621
|
-
if (sessionId) {
|
|
622
|
-
url.searchParams.set(queryParam, sessionId);
|
|
623
|
-
} else {
|
|
624
|
-
url.searchParams.delete(queryParam);
|
|
695
|
+
var fetchVoiceDeliveryRuntime = async (path = "/api/voice-delivery-runtime", options = {}) => {
|
|
696
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
697
|
+
const response = await fetchImpl(path);
|
|
698
|
+
if (!response.ok) {
|
|
699
|
+
throw new Error(`Voice delivery runtime failed: HTTP ${response.status}`);
|
|
625
700
|
}
|
|
626
|
-
return
|
|
701
|
+
return await response.json();
|
|
627
702
|
};
|
|
628
|
-
var
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
if (!
|
|
634
|
-
|
|
703
|
+
var runVoiceDeliveryRuntimeAction = async (action, path = "/api/voice-delivery-runtime", options = {}) => {
|
|
704
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
705
|
+
const response = await fetchImpl(getDefaultActionPath(path, action, options), {
|
|
706
|
+
method: "POST"
|
|
707
|
+
});
|
|
708
|
+
if (!response.ok) {
|
|
709
|
+
throw new Error(`Voice delivery runtime ${action} failed: HTTP ${response.status}`);
|
|
635
710
|
}
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
element.setAttribute("hx-get", nextRoute);
|
|
643
|
-
}
|
|
644
|
-
htmxWindow.htmx?.process?.(element);
|
|
645
|
-
htmxWindow.htmx?.trigger?.(element, eventName);
|
|
646
|
-
};
|
|
647
|
-
const unsubscribe = stream.subscribe(sync);
|
|
648
|
-
sync();
|
|
649
|
-
return () => {
|
|
650
|
-
unsubscribe();
|
|
711
|
+
const body = await response.json();
|
|
712
|
+
return {
|
|
713
|
+
action,
|
|
714
|
+
result: body.result,
|
|
715
|
+
summary: body.summary,
|
|
716
|
+
updatedAt: Date.now()
|
|
651
717
|
};
|
|
652
718
|
};
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
if (bytes.byteLength < 2) {
|
|
667
|
-
return 0;
|
|
668
|
-
}
|
|
669
|
-
const samples = new Int16Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 2));
|
|
670
|
-
if (samples.length === 0) {
|
|
671
|
-
return 0;
|
|
672
|
-
}
|
|
673
|
-
let sumSquares = 0;
|
|
674
|
-
for (const sample of samples) {
|
|
675
|
-
const normalized = sample / 32768;
|
|
676
|
-
sumSquares += normalized * normalized;
|
|
677
|
-
}
|
|
678
|
-
return Math.min(1, Math.max(0, Math.sqrt(sumSquares / samples.length) * 5.5));
|
|
679
|
-
};
|
|
680
|
-
var downsampleBuffer = (input, sourceRate, targetRate) => {
|
|
681
|
-
if (sourceRate === targetRate) {
|
|
682
|
-
return input;
|
|
683
|
-
}
|
|
684
|
-
const ratio = sourceRate / targetRate;
|
|
685
|
-
const length = Math.round(input.length / ratio);
|
|
686
|
-
const output = new Float32Array(length);
|
|
687
|
-
let offsetResult = 0;
|
|
688
|
-
let offsetBuffer = 0;
|
|
689
|
-
while (offsetResult < output.length) {
|
|
690
|
-
const nextOffsetBuffer = Math.round((offsetResult + 1) * ratio);
|
|
691
|
-
let accum = 0;
|
|
692
|
-
let count = 0;
|
|
693
|
-
for (let index = offsetBuffer;index < nextOffsetBuffer && index < input.length; index += 1) {
|
|
694
|
-
accum += input[index] ?? 0;
|
|
695
|
-
count += 1;
|
|
719
|
+
var createVoiceDeliveryRuntimeStore = (path = "/api/voice-delivery-runtime", options = {}) => {
|
|
720
|
+
const listeners = new Set;
|
|
721
|
+
let closed = false;
|
|
722
|
+
let timer;
|
|
723
|
+
let snapshot = {
|
|
724
|
+
actionError: null,
|
|
725
|
+
actionStatus: "idle",
|
|
726
|
+
error: null,
|
|
727
|
+
isLoading: false
|
|
728
|
+
};
|
|
729
|
+
const emit = () => {
|
|
730
|
+
for (const listener of listeners) {
|
|
731
|
+
listener();
|
|
696
732
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
return output;
|
|
702
|
-
};
|
|
703
|
-
var createMicrophoneCapture = (options) => {
|
|
704
|
-
let audioContext = null;
|
|
705
|
-
let sourceNode = null;
|
|
706
|
-
let processorNode = null;
|
|
707
|
-
let mediaStream = null;
|
|
708
|
-
const start = async () => {
|
|
709
|
-
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
710
|
-
throw new Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");
|
|
733
|
+
};
|
|
734
|
+
const refresh = async () => {
|
|
735
|
+
if (closed) {
|
|
736
|
+
return snapshot.report;
|
|
711
737
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
738
|
+
snapshot = {
|
|
739
|
+
...snapshot,
|
|
740
|
+
error: null,
|
|
741
|
+
isLoading: true
|
|
742
|
+
};
|
|
743
|
+
emit();
|
|
744
|
+
try {
|
|
745
|
+
const report = await fetchVoiceDeliveryRuntime(path, options);
|
|
746
|
+
snapshot = {
|
|
747
|
+
...snapshot,
|
|
748
|
+
error: null,
|
|
749
|
+
isLoading: false,
|
|
750
|
+
report,
|
|
751
|
+
updatedAt: Date.now()
|
|
752
|
+
};
|
|
753
|
+
emit();
|
|
754
|
+
return report;
|
|
755
|
+
} catch (error) {
|
|
756
|
+
snapshot = {
|
|
757
|
+
...snapshot,
|
|
758
|
+
error: error instanceof Error ? error.message : String(error),
|
|
759
|
+
isLoading: false
|
|
760
|
+
};
|
|
761
|
+
emit();
|
|
762
|
+
throw error;
|
|
715
763
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const channel = event.inputBuffer.getChannelData(0);
|
|
726
|
-
const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
|
|
727
|
-
const pcm = floatTo16BitPCM(downsampled);
|
|
728
|
-
options.onLevel?.(getPcmLevel(pcm));
|
|
729
|
-
options.onAudio(pcm);
|
|
764
|
+
};
|
|
765
|
+
const runAction = async (action) => {
|
|
766
|
+
if (closed) {
|
|
767
|
+
return snapshot.lastAction;
|
|
768
|
+
}
|
|
769
|
+
snapshot = {
|
|
770
|
+
...snapshot,
|
|
771
|
+
actionError: null,
|
|
772
|
+
actionStatus: "running"
|
|
730
773
|
};
|
|
731
|
-
|
|
732
|
-
|
|
774
|
+
emit();
|
|
775
|
+
try {
|
|
776
|
+
const result = await runVoiceDeliveryRuntimeAction(action, path, options);
|
|
777
|
+
snapshot = {
|
|
778
|
+
...snapshot,
|
|
779
|
+
actionError: null,
|
|
780
|
+
actionStatus: "completed",
|
|
781
|
+
lastAction: result
|
|
782
|
+
};
|
|
783
|
+
emit();
|
|
784
|
+
await refresh();
|
|
785
|
+
return result;
|
|
786
|
+
} catch (error) {
|
|
787
|
+
snapshot = {
|
|
788
|
+
...snapshot,
|
|
789
|
+
actionError: error instanceof Error ? error.message : String(error),
|
|
790
|
+
actionStatus: "failed"
|
|
791
|
+
};
|
|
792
|
+
emit();
|
|
793
|
+
throw error;
|
|
794
|
+
}
|
|
733
795
|
};
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
mediaStream = null;
|
|
742
|
-
processorNode = null;
|
|
743
|
-
sourceNode = null;
|
|
796
|
+
const close = () => {
|
|
797
|
+
closed = true;
|
|
798
|
+
if (timer) {
|
|
799
|
+
clearInterval(timer);
|
|
800
|
+
timer = undefined;
|
|
801
|
+
}
|
|
802
|
+
listeners.clear();
|
|
744
803
|
};
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
var DEFAULT_TARGET_LEVEL = 0.08;
|
|
750
|
-
var DEFAULT_MAX_GAIN = 3;
|
|
751
|
-
var DEFAULT_NOISE_GATE_THRESHOLD = 0.006;
|
|
752
|
-
var DEFAULT_NOISE_GATE_ATTENUATION = 0.15;
|
|
753
|
-
var toInt16Array = (audio) => {
|
|
754
|
-
if (audio instanceof ArrayBuffer) {
|
|
755
|
-
return new Int16Array(audio, 0, Math.floor(audio.byteLength / 2));
|
|
804
|
+
if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
|
|
805
|
+
timer = setInterval(() => {
|
|
806
|
+
refresh().catch(() => {});
|
|
807
|
+
}, options.intervalMs);
|
|
756
808
|
}
|
|
757
|
-
return
|
|
809
|
+
return {
|
|
810
|
+
close,
|
|
811
|
+
getServerSnapshot: () => snapshot,
|
|
812
|
+
getSnapshot: () => snapshot,
|
|
813
|
+
requeueDeadLetters: () => runAction("requeue-dead-letters"),
|
|
814
|
+
refresh,
|
|
815
|
+
tick: () => runAction("tick"),
|
|
816
|
+
subscribe: (listener) => {
|
|
817
|
+
listeners.add(listener);
|
|
818
|
+
return () => {
|
|
819
|
+
listeners.delete(listener);
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
};
|
|
758
823
|
};
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
824
|
+
|
|
825
|
+
// src/angular/voice-delivery-runtime.service.ts
|
|
826
|
+
var _dec = [
|
|
827
|
+
Injectable5({ providedIn: "root" })
|
|
828
|
+
];
|
|
829
|
+
var _init = __decoratorStart(undefined);
|
|
830
|
+
|
|
831
|
+
class VoiceDeliveryRuntimeService {
|
|
832
|
+
connect(path = "/api/voice-delivery-runtime", options = {}) {
|
|
833
|
+
const store = createVoiceDeliveryRuntimeStore(path, options);
|
|
834
|
+
const actionErrorSignal = signal5(null);
|
|
835
|
+
const actionStatusSignal = signal5("idle");
|
|
836
|
+
const errorSignal = signal5(null);
|
|
837
|
+
const isLoadingSignal = signal5(false);
|
|
838
|
+
const reportSignal = signal5(undefined);
|
|
839
|
+
const updatedAtSignal = signal5(undefined);
|
|
840
|
+
const sync = () => {
|
|
841
|
+
const snapshot = store.getSnapshot();
|
|
842
|
+
actionErrorSignal.set(snapshot.actionError);
|
|
843
|
+
actionStatusSignal.set(snapshot.actionStatus);
|
|
844
|
+
errorSignal.set(snapshot.error);
|
|
845
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
846
|
+
reportSignal.set(snapshot.report);
|
|
847
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
848
|
+
};
|
|
849
|
+
const unsubscribe = store.subscribe(sync);
|
|
850
|
+
sync();
|
|
851
|
+
if (typeof window !== "undefined") {
|
|
852
|
+
store.refresh().catch(() => {});
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
close: () => {
|
|
856
|
+
unsubscribe();
|
|
857
|
+
store.close();
|
|
858
|
+
},
|
|
859
|
+
error: computed5(() => errorSignal()),
|
|
860
|
+
actionError: computed5(() => actionErrorSignal()),
|
|
861
|
+
actionStatus: computed5(() => actionStatusSignal()),
|
|
862
|
+
isLoading: computed5(() => isLoadingSignal()),
|
|
863
|
+
requeueDeadLetters: store.requeueDeadLetters,
|
|
864
|
+
refresh: store.refresh,
|
|
865
|
+
report: computed5(() => reportSignal()),
|
|
866
|
+
tick: store.tick,
|
|
867
|
+
updatedAt: computed5(() => updatedAtSignal())
|
|
868
|
+
};
|
|
762
869
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
870
|
+
}
|
|
871
|
+
VoiceDeliveryRuntimeService = __decorateElement(_init, 0, "VoiceDeliveryRuntimeService", _dec, VoiceDeliveryRuntimeService);
|
|
872
|
+
__runInitializers(_init, 1, VoiceDeliveryRuntimeService);
|
|
873
|
+
__decoratorMetadata(_init, VoiceDeliveryRuntimeService);
|
|
874
|
+
let _VoiceDeliveryRuntimeService = VoiceDeliveryRuntimeService;
|
|
875
|
+
// src/angular/voice-campaign-dialer-proof.service.ts
|
|
876
|
+
import { computed as computed6, Injectable as Injectable6, signal as signal6 } from "@angular/core";
|
|
877
|
+
|
|
878
|
+
// src/client/campaignDialerProof.ts
|
|
879
|
+
var fetchVoiceCampaignDialerProofStatus = async (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
|
|
880
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
881
|
+
const response = await fetchImpl(path);
|
|
882
|
+
if (!response.ok) {
|
|
883
|
+
throw new Error(`Voice campaign dialer proof status failed: HTTP ${response.status}`);
|
|
767
884
|
}
|
|
768
|
-
return
|
|
885
|
+
return await response.json();
|
|
769
886
|
};
|
|
770
|
-
var
|
|
771
|
-
|
|
772
|
-
|
|
887
|
+
var runVoiceCampaignDialerProofAction = async (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
|
|
888
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
889
|
+
const response = await fetchImpl(path, { method: "POST" });
|
|
890
|
+
if (!response.ok) {
|
|
891
|
+
throw new Error(`Voice campaign dialer proof failed: HTTP ${response.status}`);
|
|
773
892
|
}
|
|
774
|
-
return
|
|
775
|
-
|
|
893
|
+
return await response.json();
|
|
894
|
+
};
|
|
895
|
+
var createVoiceCampaignDialerProofStore = (path = "/api/voice/campaigns/dialer-proof", options = {}) => {
|
|
896
|
+
const listeners = new Set;
|
|
897
|
+
let closed = false;
|
|
898
|
+
let timer;
|
|
899
|
+
let snapshot = {
|
|
900
|
+
error: null,
|
|
901
|
+
isLoading: false
|
|
902
|
+
};
|
|
903
|
+
const emit = () => {
|
|
904
|
+
for (const listener of listeners) {
|
|
905
|
+
listener();
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
const refresh = async () => {
|
|
909
|
+
if (closed) {
|
|
910
|
+
return snapshot.status;
|
|
911
|
+
}
|
|
912
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
913
|
+
emit();
|
|
914
|
+
try {
|
|
915
|
+
const status = await fetchVoiceCampaignDialerProofStatus(path, options);
|
|
916
|
+
snapshot = {
|
|
917
|
+
...snapshot,
|
|
918
|
+
error: null,
|
|
919
|
+
isLoading: false,
|
|
920
|
+
status,
|
|
921
|
+
updatedAt: Date.now()
|
|
922
|
+
};
|
|
923
|
+
emit();
|
|
924
|
+
return status;
|
|
925
|
+
} catch (error) {
|
|
926
|
+
snapshot = {
|
|
927
|
+
...snapshot,
|
|
928
|
+
error: error instanceof Error ? error.message : String(error),
|
|
929
|
+
isLoading: false
|
|
930
|
+
};
|
|
931
|
+
emit();
|
|
932
|
+
throw error;
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
const runProof = async () => {
|
|
936
|
+
const runPath = options.runPath ?? snapshot.status?.runPath ?? path;
|
|
937
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
938
|
+
emit();
|
|
939
|
+
try {
|
|
940
|
+
const report = await runVoiceCampaignDialerProofAction(runPath, options);
|
|
941
|
+
snapshot = {
|
|
942
|
+
...snapshot,
|
|
943
|
+
error: null,
|
|
944
|
+
isLoading: false,
|
|
945
|
+
report,
|
|
946
|
+
status: {
|
|
947
|
+
generatedAt: Date.now(),
|
|
948
|
+
mode: report.mode,
|
|
949
|
+
ok: report.ok,
|
|
950
|
+
providers: report.providers.map((provider) => provider.provider),
|
|
951
|
+
runPath,
|
|
952
|
+
safe: true
|
|
953
|
+
},
|
|
954
|
+
updatedAt: Date.now()
|
|
955
|
+
};
|
|
956
|
+
emit();
|
|
957
|
+
return report;
|
|
958
|
+
} catch (error) {
|
|
959
|
+
snapshot = {
|
|
960
|
+
...snapshot,
|
|
961
|
+
error: error instanceof Error ? error.message : String(error),
|
|
962
|
+
isLoading: false
|
|
963
|
+
};
|
|
964
|
+
emit();
|
|
965
|
+
throw error;
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
const close = () => {
|
|
969
|
+
closed = true;
|
|
970
|
+
if (timer) {
|
|
971
|
+
clearInterval(timer);
|
|
972
|
+
timer = undefined;
|
|
973
|
+
}
|
|
974
|
+
listeners.clear();
|
|
975
|
+
};
|
|
976
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
977
|
+
timer = setInterval(() => {
|
|
978
|
+
refresh().catch(() => {});
|
|
979
|
+
}, options.intervalMs);
|
|
980
|
+
}
|
|
981
|
+
return {
|
|
982
|
+
close,
|
|
983
|
+
getServerSnapshot: () => snapshot,
|
|
984
|
+
getSnapshot: () => snapshot,
|
|
985
|
+
refresh,
|
|
986
|
+
runProof,
|
|
987
|
+
subscribe: (listener) => {
|
|
988
|
+
listeners.add(listener);
|
|
989
|
+
return () => {
|
|
990
|
+
listeners.delete(listener);
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
// src/angular/voice-campaign-dialer-proof.service.ts
|
|
997
|
+
var _dec = [
|
|
998
|
+
Injectable6({ providedIn: "root" })
|
|
999
|
+
];
|
|
1000
|
+
var _init = __decoratorStart(undefined);
|
|
1001
|
+
|
|
1002
|
+
class VoiceCampaignDialerProofService {
|
|
1003
|
+
connect(path = "/api/voice/campaigns/dialer-proof", options = {}) {
|
|
1004
|
+
const store = createVoiceCampaignDialerProofStore(path, options);
|
|
1005
|
+
const errorSignal = signal6(null);
|
|
1006
|
+
const isLoadingSignal = signal6(false);
|
|
1007
|
+
const reportSignal = signal6(undefined);
|
|
1008
|
+
const statusSignal = signal6(undefined);
|
|
1009
|
+
const updatedAtSignal = signal6(undefined);
|
|
1010
|
+
const sync = () => {
|
|
1011
|
+
const snapshot = store.getSnapshot();
|
|
1012
|
+
errorSignal.set(snapshot.error);
|
|
1013
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
1014
|
+
reportSignal.set(snapshot.report);
|
|
1015
|
+
statusSignal.set(snapshot.status);
|
|
1016
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
1017
|
+
};
|
|
1018
|
+
const unsubscribe = store.subscribe(sync);
|
|
1019
|
+
sync();
|
|
1020
|
+
store.refresh().catch(() => {});
|
|
1021
|
+
return {
|
|
1022
|
+
close: () => {
|
|
1023
|
+
unsubscribe();
|
|
1024
|
+
store.close();
|
|
1025
|
+
},
|
|
1026
|
+
error: computed6(() => errorSignal()),
|
|
1027
|
+
isLoading: computed6(() => isLoadingSignal()),
|
|
1028
|
+
refresh: store.refresh,
|
|
1029
|
+
report: computed6(() => reportSignal()),
|
|
1030
|
+
runProof: store.runProof,
|
|
1031
|
+
status: computed6(() => statusSignal()),
|
|
1032
|
+
updatedAt: computed6(() => updatedAtSignal())
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
VoiceCampaignDialerProofService = __decorateElement(_init, 0, "VoiceCampaignDialerProofService", _dec, VoiceCampaignDialerProofService);
|
|
1037
|
+
__runInitializers(_init, 1, VoiceCampaignDialerProofService);
|
|
1038
|
+
__decoratorMetadata(_init, VoiceCampaignDialerProofService);
|
|
1039
|
+
let _VoiceCampaignDialerProofService = VoiceCampaignDialerProofService;
|
|
1040
|
+
// src/angular/voice-stream.service.ts
|
|
1041
|
+
import { computed as computed7, Injectable as Injectable7, signal as signal7 } from "@angular/core";
|
|
1042
|
+
|
|
1043
|
+
// src/client/actions.ts
|
|
1044
|
+
var normalizeErrorMessage = (value) => {
|
|
1045
|
+
if (typeof value === "string" && value.trim()) {
|
|
1046
|
+
return value;
|
|
1047
|
+
}
|
|
1048
|
+
if (value instanceof Error && value.message.trim()) {
|
|
1049
|
+
return value.message;
|
|
1050
|
+
}
|
|
1051
|
+
if (value && typeof value === "object") {
|
|
1052
|
+
const record = value;
|
|
1053
|
+
for (const key of ["message", "reason", "description"]) {
|
|
1054
|
+
const candidate = record[key];
|
|
1055
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
1056
|
+
return candidate;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if ("error" in record) {
|
|
1060
|
+
return normalizeErrorMessage(record.error);
|
|
1061
|
+
}
|
|
1062
|
+
if ("cause" in record) {
|
|
1063
|
+
return normalizeErrorMessage(record.cause);
|
|
1064
|
+
}
|
|
1065
|
+
try {
|
|
1066
|
+
return JSON.stringify(value);
|
|
1067
|
+
} catch {}
|
|
1068
|
+
}
|
|
1069
|
+
return "Unexpected error";
|
|
1070
|
+
};
|
|
1071
|
+
var serverMessageToAction = (message) => {
|
|
1072
|
+
switch (message.type) {
|
|
1073
|
+
case "audio":
|
|
1074
|
+
return {
|
|
1075
|
+
chunk: Uint8Array.from(atob(message.chunkBase64), (char) => char.charCodeAt(0)),
|
|
1076
|
+
format: message.format,
|
|
1077
|
+
receivedAt: message.receivedAt,
|
|
1078
|
+
turnId: message.turnId,
|
|
1079
|
+
type: "audio"
|
|
1080
|
+
};
|
|
1081
|
+
case "assistant":
|
|
1082
|
+
return {
|
|
1083
|
+
text: message.text,
|
|
1084
|
+
type: "assistant"
|
|
1085
|
+
};
|
|
1086
|
+
case "complete":
|
|
1087
|
+
return {
|
|
1088
|
+
sessionId: message.sessionId,
|
|
1089
|
+
type: "complete"
|
|
1090
|
+
};
|
|
1091
|
+
case "connection":
|
|
1092
|
+
return {
|
|
1093
|
+
reconnect: message.reconnect,
|
|
1094
|
+
type: "connection"
|
|
1095
|
+
};
|
|
1096
|
+
case "call_lifecycle":
|
|
1097
|
+
return {
|
|
1098
|
+
event: message.event,
|
|
1099
|
+
sessionId: message.sessionId,
|
|
1100
|
+
type: "call_lifecycle"
|
|
1101
|
+
};
|
|
1102
|
+
case "error":
|
|
1103
|
+
return {
|
|
1104
|
+
message: normalizeErrorMessage(message.message),
|
|
1105
|
+
type: "error"
|
|
1106
|
+
};
|
|
1107
|
+
case "final":
|
|
1108
|
+
return {
|
|
1109
|
+
transcript: message.transcript,
|
|
1110
|
+
type: "final"
|
|
1111
|
+
};
|
|
1112
|
+
case "partial":
|
|
1113
|
+
return {
|
|
1114
|
+
transcript: message.transcript,
|
|
1115
|
+
type: "partial"
|
|
1116
|
+
};
|
|
1117
|
+
case "replay":
|
|
1118
|
+
return {
|
|
1119
|
+
assistantTexts: message.assistantTexts,
|
|
1120
|
+
call: message.call,
|
|
1121
|
+
partial: message.partial,
|
|
1122
|
+
scenarioId: message.scenarioId,
|
|
1123
|
+
sessionId: message.sessionId,
|
|
1124
|
+
status: message.status,
|
|
1125
|
+
turns: message.turns,
|
|
1126
|
+
type: "replay"
|
|
1127
|
+
};
|
|
1128
|
+
case "session":
|
|
1129
|
+
return {
|
|
1130
|
+
sessionId: message.sessionId,
|
|
1131
|
+
scenarioId: message.scenarioId,
|
|
1132
|
+
status: message.status,
|
|
1133
|
+
type: "session"
|
|
1134
|
+
};
|
|
1135
|
+
case "turn":
|
|
1136
|
+
return {
|
|
1137
|
+
turn: message.turn,
|
|
1138
|
+
type: "turn"
|
|
1139
|
+
};
|
|
1140
|
+
default:
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
// src/client/connection.ts
|
|
1146
|
+
var WS_OPEN = 1;
|
|
1147
|
+
var WS_CLOSED = 3;
|
|
1148
|
+
var WS_NORMAL_CLOSURE = 1000;
|
|
1149
|
+
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;
|
|
1150
|
+
var DEFAULT_PING_INTERVAL = 30000;
|
|
1151
|
+
var RECONNECT_DELAY_MS = 500;
|
|
1152
|
+
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
1153
|
+
var noop = () => {};
|
|
1154
|
+
var noopUnsubscribe = () => noop;
|
|
1155
|
+
var NOOP_CONNECTION = {
|
|
1156
|
+
callControl: noop,
|
|
1157
|
+
close: noop,
|
|
1158
|
+
endTurn: noop,
|
|
1159
|
+
getReadyState: () => WS_CLOSED,
|
|
1160
|
+
getScenarioId: () => "",
|
|
1161
|
+
getSessionId: () => "",
|
|
1162
|
+
send: noop,
|
|
1163
|
+
sendAudio: noop,
|
|
1164
|
+
start: () => {},
|
|
1165
|
+
subscribe: noopUnsubscribe
|
|
1166
|
+
};
|
|
1167
|
+
var createSessionId = () => crypto.randomUUID();
|
|
1168
|
+
var buildWsUrl = (path, sessionId, scenarioId) => {
|
|
1169
|
+
const { hostname, port, protocol } = window.location;
|
|
1170
|
+
const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
|
|
1171
|
+
const portSuffix = port ? `:${port}` : "";
|
|
1172
|
+
const url = new URL(`${wsProtocol}//${hostname}${portSuffix}${path}`);
|
|
1173
|
+
url.searchParams.set("sessionId", sessionId);
|
|
1174
|
+
if (scenarioId) {
|
|
1175
|
+
url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
|
|
1176
|
+
}
|
|
1177
|
+
return url.toString();
|
|
1178
|
+
};
|
|
1179
|
+
var isVoiceServerMessage = (value) => {
|
|
1180
|
+
if (!value || typeof value !== "object" || !("type" in value)) {
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
switch (value.type) {
|
|
1184
|
+
case "audio":
|
|
1185
|
+
case "assistant":
|
|
1186
|
+
case "call_lifecycle":
|
|
1187
|
+
case "complete":
|
|
1188
|
+
case "connection":
|
|
1189
|
+
case "error":
|
|
1190
|
+
case "final":
|
|
1191
|
+
case "partial":
|
|
1192
|
+
case "pong":
|
|
1193
|
+
case "replay":
|
|
1194
|
+
case "session":
|
|
1195
|
+
case "turn":
|
|
1196
|
+
return true;
|
|
1197
|
+
default:
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
var parseServerMessage = (event) => {
|
|
1202
|
+
if (typeof event.data !== "string") {
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
try {
|
|
1206
|
+
const parsed = JSON.parse(event.data);
|
|
1207
|
+
return isVoiceServerMessage(parsed) ? parsed : null;
|
|
1208
|
+
} catch {
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
var createVoiceConnection = (path, options = {}) => {
|
|
1213
|
+
if (typeof window === "undefined") {
|
|
1214
|
+
return NOOP_CONNECTION;
|
|
1215
|
+
}
|
|
1216
|
+
const listeners = new Set;
|
|
1217
|
+
const shouldReconnect = options.reconnect !== false;
|
|
1218
|
+
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
1219
|
+
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
1220
|
+
const state = {
|
|
1221
|
+
isConnected: false,
|
|
1222
|
+
pendingMessages: [],
|
|
1223
|
+
scenarioId: options.scenarioId ?? null,
|
|
1224
|
+
pingInterval: null,
|
|
1225
|
+
reconnectAttempts: 0,
|
|
1226
|
+
reconnectTimeout: null,
|
|
1227
|
+
sessionId: options.sessionId ?? createSessionId(),
|
|
1228
|
+
ws: null
|
|
1229
|
+
};
|
|
1230
|
+
const emitConnection = (reconnect) => {
|
|
1231
|
+
listeners.forEach((listener) => listener(reconnect));
|
|
1232
|
+
};
|
|
1233
|
+
const clearTimers = () => {
|
|
1234
|
+
if (state.pingInterval) {
|
|
1235
|
+
clearInterval(state.pingInterval);
|
|
1236
|
+
state.pingInterval = null;
|
|
1237
|
+
}
|
|
1238
|
+
if (state.reconnectTimeout) {
|
|
1239
|
+
clearTimeout(state.reconnectTimeout);
|
|
1240
|
+
state.reconnectTimeout = null;
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
const flushPendingMessages = () => {
|
|
1244
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
while (state.pendingMessages.length > 0) {
|
|
1248
|
+
const next = state.pendingMessages.shift();
|
|
1249
|
+
if (next !== undefined) {
|
|
1250
|
+
state.ws.send(next);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
const scheduleReconnect = () => {
|
|
1255
|
+
const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
|
|
1256
|
+
state.reconnectAttempts += 1;
|
|
1257
|
+
emitConnection({
|
|
1258
|
+
reconnect: {
|
|
1259
|
+
attempts: state.reconnectAttempts,
|
|
1260
|
+
lastDisconnectAt: Date.now(),
|
|
1261
|
+
maxAttempts: maxReconnectAttempts,
|
|
1262
|
+
nextAttemptAt,
|
|
1263
|
+
status: "reconnecting"
|
|
1264
|
+
},
|
|
1265
|
+
type: "connection"
|
|
1266
|
+
});
|
|
1267
|
+
state.reconnectTimeout = setTimeout(() => {
|
|
1268
|
+
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
1269
|
+
emitConnection({
|
|
1270
|
+
reconnect: {
|
|
1271
|
+
attempts: state.reconnectAttempts,
|
|
1272
|
+
maxAttempts: maxReconnectAttempts,
|
|
1273
|
+
status: "exhausted"
|
|
1274
|
+
},
|
|
1275
|
+
type: "connection"
|
|
1276
|
+
});
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
connect();
|
|
1280
|
+
}, RECONNECT_DELAY_MS);
|
|
1281
|
+
};
|
|
1282
|
+
const connect = () => {
|
|
1283
|
+
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
|
1284
|
+
ws.binaryType = "arraybuffer";
|
|
1285
|
+
ws.onopen = () => {
|
|
1286
|
+
const wasReconnecting = state.reconnectAttempts > 0;
|
|
1287
|
+
state.isConnected = true;
|
|
1288
|
+
flushPendingMessages();
|
|
1289
|
+
if (wasReconnecting) {
|
|
1290
|
+
emitConnection({
|
|
1291
|
+
reconnect: {
|
|
1292
|
+
attempts: state.reconnectAttempts,
|
|
1293
|
+
lastResumedAt: Date.now(),
|
|
1294
|
+
maxAttempts: maxReconnectAttempts,
|
|
1295
|
+
status: "resumed"
|
|
1296
|
+
},
|
|
1297
|
+
type: "connection"
|
|
1298
|
+
});
|
|
1299
|
+
state.reconnectAttempts = 0;
|
|
1300
|
+
}
|
|
1301
|
+
listeners.forEach((listener) => listener({
|
|
1302
|
+
scenarioId: state.scenarioId ?? undefined,
|
|
1303
|
+
sessionId: state.sessionId,
|
|
1304
|
+
status: "active",
|
|
1305
|
+
type: "session"
|
|
1306
|
+
}));
|
|
1307
|
+
state.pingInterval = setInterval(() => {
|
|
1308
|
+
if (ws.readyState === WS_OPEN) {
|
|
1309
|
+
ws.send(JSON.stringify({ type: "ping" }));
|
|
1310
|
+
}
|
|
1311
|
+
}, pingInterval);
|
|
1312
|
+
};
|
|
1313
|
+
ws.onmessage = (event) => {
|
|
1314
|
+
const parsed = parseServerMessage(event);
|
|
1315
|
+
if (!parsed) {
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
if (parsed.type === "session") {
|
|
1319
|
+
state.sessionId = parsed.sessionId;
|
|
1320
|
+
state.scenarioId = parsed.scenarioId ?? state.scenarioId;
|
|
1321
|
+
}
|
|
1322
|
+
listeners.forEach((listener) => listener(parsed));
|
|
1323
|
+
};
|
|
1324
|
+
ws.onclose = (event) => {
|
|
1325
|
+
state.isConnected = false;
|
|
1326
|
+
clearTimers();
|
|
1327
|
+
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
1328
|
+
if (reconnectable) {
|
|
1329
|
+
scheduleReconnect();
|
|
1330
|
+
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
1331
|
+
emitConnection({
|
|
1332
|
+
reconnect: {
|
|
1333
|
+
attempts: state.reconnectAttempts,
|
|
1334
|
+
lastDisconnectAt: Date.now(),
|
|
1335
|
+
maxAttempts: maxReconnectAttempts,
|
|
1336
|
+
status: "exhausted"
|
|
1337
|
+
},
|
|
1338
|
+
type: "connection"
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
state.ws = ws;
|
|
1343
|
+
};
|
|
1344
|
+
const sendSerialized = (value) => {
|
|
1345
|
+
if (state.ws?.readyState === WS_OPEN) {
|
|
1346
|
+
state.ws.send(value);
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
state.pendingMessages.push(value);
|
|
1350
|
+
};
|
|
1351
|
+
const send = (message) => {
|
|
1352
|
+
sendSerialized(JSON.stringify(message));
|
|
1353
|
+
};
|
|
1354
|
+
const start = (input = {}) => {
|
|
1355
|
+
if (input.sessionId) {
|
|
1356
|
+
state.sessionId = input.sessionId;
|
|
1357
|
+
}
|
|
1358
|
+
if (input.scenarioId) {
|
|
1359
|
+
state.scenarioId = input.scenarioId;
|
|
1360
|
+
}
|
|
1361
|
+
send({
|
|
1362
|
+
type: "start",
|
|
1363
|
+
sessionId: state.sessionId,
|
|
1364
|
+
scenarioId: state.scenarioId ?? undefined
|
|
1365
|
+
});
|
|
1366
|
+
};
|
|
1367
|
+
const sendAudio = (audio) => {
|
|
1368
|
+
sendSerialized(audio);
|
|
1369
|
+
};
|
|
1370
|
+
const endTurn = () => {
|
|
1371
|
+
send({ type: "end_turn" });
|
|
1372
|
+
};
|
|
1373
|
+
const callControl = (message) => {
|
|
1374
|
+
send({
|
|
1375
|
+
...message,
|
|
1376
|
+
type: "call_control"
|
|
1377
|
+
});
|
|
1378
|
+
};
|
|
1379
|
+
const close = () => {
|
|
1380
|
+
clearTimers();
|
|
1381
|
+
if (state.ws) {
|
|
1382
|
+
state.ws.close(WS_NORMAL_CLOSURE);
|
|
1383
|
+
state.ws = null;
|
|
1384
|
+
}
|
|
1385
|
+
state.isConnected = false;
|
|
1386
|
+
listeners.clear();
|
|
1387
|
+
};
|
|
1388
|
+
const subscribe = (callback) => {
|
|
1389
|
+
listeners.add(callback);
|
|
1390
|
+
return () => {
|
|
1391
|
+
listeners.delete(callback);
|
|
1392
|
+
};
|
|
1393
|
+
};
|
|
1394
|
+
connect();
|
|
1395
|
+
return {
|
|
1396
|
+
callControl,
|
|
1397
|
+
close,
|
|
1398
|
+
endTurn,
|
|
1399
|
+
getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
|
|
1400
|
+
getScenarioId: () => state.scenarioId ?? "",
|
|
1401
|
+
getSessionId: () => state.sessionId,
|
|
1402
|
+
send,
|
|
1403
|
+
sendAudio,
|
|
1404
|
+
start,
|
|
1405
|
+
subscribe
|
|
1406
|
+
};
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
// src/client/store.ts
|
|
1410
|
+
var createInitialReconnectState = () => ({
|
|
1411
|
+
attempts: 0,
|
|
1412
|
+
maxAttempts: 0,
|
|
1413
|
+
status: "idle"
|
|
1414
|
+
});
|
|
1415
|
+
var createInitialState = () => ({
|
|
1416
|
+
assistantAudio: [],
|
|
1417
|
+
assistantTexts: [],
|
|
1418
|
+
call: null,
|
|
1419
|
+
error: null,
|
|
1420
|
+
isConnected: false,
|
|
1421
|
+
scenarioId: null,
|
|
1422
|
+
partial: "",
|
|
1423
|
+
reconnect: createInitialReconnectState(),
|
|
1424
|
+
sessionId: null,
|
|
1425
|
+
status: "idle",
|
|
1426
|
+
turns: []
|
|
1427
|
+
});
|
|
1428
|
+
var createVoiceStreamStore = () => {
|
|
1429
|
+
let state = createInitialState();
|
|
1430
|
+
const subscribers = new Set;
|
|
1431
|
+
const notify = () => {
|
|
1432
|
+
subscribers.forEach((subscriber) => subscriber());
|
|
1433
|
+
};
|
|
1434
|
+
const dispatch = (action) => {
|
|
1435
|
+
switch (action.type) {
|
|
1436
|
+
case "audio":
|
|
1437
|
+
state = {
|
|
1438
|
+
...state,
|
|
1439
|
+
assistantAudio: [
|
|
1440
|
+
...state.assistantAudio,
|
|
1441
|
+
{
|
|
1442
|
+
chunk: action.chunk,
|
|
1443
|
+
format: action.format,
|
|
1444
|
+
receivedAt: action.receivedAt,
|
|
1445
|
+
turnId: action.turnId
|
|
1446
|
+
}
|
|
1447
|
+
]
|
|
1448
|
+
};
|
|
1449
|
+
break;
|
|
1450
|
+
case "assistant":
|
|
1451
|
+
state = {
|
|
1452
|
+
...state,
|
|
1453
|
+
assistantTexts: [...state.assistantTexts, action.text]
|
|
1454
|
+
};
|
|
1455
|
+
break;
|
|
1456
|
+
case "complete":
|
|
1457
|
+
state = {
|
|
1458
|
+
...state,
|
|
1459
|
+
sessionId: action.sessionId,
|
|
1460
|
+
status: "completed"
|
|
1461
|
+
};
|
|
1462
|
+
break;
|
|
1463
|
+
case "call_lifecycle":
|
|
1464
|
+
state = {
|
|
1465
|
+
...state,
|
|
1466
|
+
call: {
|
|
1467
|
+
...state.call,
|
|
1468
|
+
disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
|
|
1469
|
+
endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
|
|
1470
|
+
events: [...state.call?.events ?? [], action.event],
|
|
1471
|
+
lastEventAt: action.event.at,
|
|
1472
|
+
startedAt: state.call?.startedAt ?? action.event.at
|
|
1473
|
+
},
|
|
1474
|
+
sessionId: action.sessionId
|
|
1475
|
+
};
|
|
1476
|
+
break;
|
|
1477
|
+
case "connected":
|
|
1478
|
+
state = {
|
|
1479
|
+
...state,
|
|
1480
|
+
isConnected: true,
|
|
1481
|
+
reconnect: state.reconnect.status === "reconnecting" ? {
|
|
1482
|
+
...state.reconnect,
|
|
1483
|
+
lastResumedAt: Date.now(),
|
|
1484
|
+
nextAttemptAt: undefined,
|
|
1485
|
+
status: "resumed"
|
|
1486
|
+
} : state.reconnect
|
|
1487
|
+
};
|
|
1488
|
+
break;
|
|
1489
|
+
case "connection":
|
|
1490
|
+
state = {
|
|
1491
|
+
...state,
|
|
1492
|
+
reconnect: action.reconnect
|
|
1493
|
+
};
|
|
1494
|
+
break;
|
|
1495
|
+
case "disconnected":
|
|
1496
|
+
state = {
|
|
1497
|
+
...state,
|
|
1498
|
+
isConnected: false
|
|
1499
|
+
};
|
|
1500
|
+
break;
|
|
1501
|
+
case "error":
|
|
1502
|
+
state = {
|
|
1503
|
+
...state,
|
|
1504
|
+
error: action.message
|
|
1505
|
+
};
|
|
1506
|
+
break;
|
|
1507
|
+
case "final":
|
|
1508
|
+
state = {
|
|
1509
|
+
...state,
|
|
1510
|
+
partial: action.transcript.text,
|
|
1511
|
+
turns: state.turns.map((turn) => turn)
|
|
1512
|
+
};
|
|
1513
|
+
break;
|
|
1514
|
+
case "partial":
|
|
1515
|
+
state = {
|
|
1516
|
+
...state,
|
|
1517
|
+
partial: action.transcript.text
|
|
1518
|
+
};
|
|
1519
|
+
break;
|
|
1520
|
+
case "replay":
|
|
1521
|
+
state = {
|
|
1522
|
+
...state,
|
|
1523
|
+
assistantTexts: [...action.assistantTexts],
|
|
1524
|
+
call: action.call ?? null,
|
|
1525
|
+
error: null,
|
|
1526
|
+
isConnected: action.status === "active",
|
|
1527
|
+
partial: action.partial,
|
|
1528
|
+
reconnect: state.reconnect.status === "reconnecting" ? {
|
|
1529
|
+
...state.reconnect,
|
|
1530
|
+
lastResumedAt: Date.now(),
|
|
1531
|
+
nextAttemptAt: undefined,
|
|
1532
|
+
status: "resumed"
|
|
1533
|
+
} : state.reconnect,
|
|
1534
|
+
scenarioId: action.scenarioId ?? state.scenarioId,
|
|
1535
|
+
sessionId: action.sessionId,
|
|
1536
|
+
status: action.status,
|
|
1537
|
+
turns: [...action.turns]
|
|
1538
|
+
};
|
|
1539
|
+
break;
|
|
1540
|
+
case "session":
|
|
1541
|
+
state = {
|
|
1542
|
+
...state,
|
|
1543
|
+
error: null,
|
|
1544
|
+
scenarioId: action.scenarioId ?? state.scenarioId,
|
|
1545
|
+
isConnected: action.status === "active",
|
|
1546
|
+
sessionId: action.sessionId,
|
|
1547
|
+
status: action.status
|
|
1548
|
+
};
|
|
1549
|
+
break;
|
|
1550
|
+
case "turn":
|
|
1551
|
+
state = {
|
|
1552
|
+
...state,
|
|
1553
|
+
partial: "",
|
|
1554
|
+
turns: [...state.turns, action.turn]
|
|
1555
|
+
};
|
|
1556
|
+
break;
|
|
1557
|
+
}
|
|
1558
|
+
notify();
|
|
1559
|
+
};
|
|
1560
|
+
return {
|
|
1561
|
+
dispatch,
|
|
1562
|
+
getServerSnapshot: () => state,
|
|
1563
|
+
getSnapshot: () => state,
|
|
1564
|
+
subscribe: (subscriber) => {
|
|
1565
|
+
subscribers.add(subscriber);
|
|
1566
|
+
return () => {
|
|
1567
|
+
subscribers.delete(subscriber);
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
};
|
|
1572
|
+
|
|
1573
|
+
// src/client/createVoiceStream.ts
|
|
1574
|
+
var createVoiceStream = (path, options = {}) => {
|
|
1575
|
+
const connection = createVoiceConnection(path, options);
|
|
1576
|
+
const store = createVoiceStreamStore();
|
|
1577
|
+
const subscribers = new Set;
|
|
1578
|
+
const start = (input) => Promise.resolve().then(() => {
|
|
1579
|
+
if (!input?.sessionId && !input?.scenarioId) {
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
connection.start(input);
|
|
1583
|
+
});
|
|
1584
|
+
const notify = () => {
|
|
1585
|
+
subscribers.forEach((subscriber) => subscriber());
|
|
1586
|
+
};
|
|
1587
|
+
const reportReconnect = () => {
|
|
1588
|
+
if (!options.reconnectReportPath || typeof fetch === "undefined") {
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
const snapshot = store.getSnapshot();
|
|
1592
|
+
const body = JSON.stringify({
|
|
1593
|
+
at: Date.now(),
|
|
1594
|
+
reconnect: snapshot.reconnect,
|
|
1595
|
+
scenarioId: snapshot.scenarioId,
|
|
1596
|
+
sessionId: connection.getSessionId(),
|
|
1597
|
+
turnIds: snapshot.turns.map((turn) => turn.id)
|
|
1598
|
+
});
|
|
1599
|
+
fetch(options.reconnectReportPath, {
|
|
1600
|
+
body,
|
|
1601
|
+
headers: {
|
|
1602
|
+
"Content-Type": "application/json"
|
|
1603
|
+
},
|
|
1604
|
+
keepalive: true,
|
|
1605
|
+
method: "POST"
|
|
1606
|
+
}).catch(() => {});
|
|
1607
|
+
};
|
|
1608
|
+
const unsubscribeConnection = connection.subscribe((message) => {
|
|
1609
|
+
const action = serverMessageToAction(message);
|
|
1610
|
+
if (action) {
|
|
1611
|
+
store.dispatch(action);
|
|
1612
|
+
if (message.type === "connection") {
|
|
1613
|
+
reportReconnect();
|
|
1614
|
+
}
|
|
1615
|
+
notify();
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
return {
|
|
1619
|
+
callControl(message) {
|
|
1620
|
+
connection.callControl(message);
|
|
1621
|
+
},
|
|
1622
|
+
close() {
|
|
1623
|
+
unsubscribeConnection();
|
|
1624
|
+
connection.close();
|
|
1625
|
+
store.dispatch({ type: "disconnected" });
|
|
1626
|
+
notify();
|
|
1627
|
+
},
|
|
1628
|
+
endTurn() {
|
|
1629
|
+
connection.endTurn();
|
|
1630
|
+
},
|
|
1631
|
+
get error() {
|
|
1632
|
+
return store.getSnapshot().error;
|
|
1633
|
+
},
|
|
1634
|
+
getServerSnapshot() {
|
|
1635
|
+
return store.getServerSnapshot();
|
|
1636
|
+
},
|
|
1637
|
+
getSnapshot() {
|
|
1638
|
+
return store.getSnapshot();
|
|
1639
|
+
},
|
|
1640
|
+
get isConnected() {
|
|
1641
|
+
return store.getSnapshot().isConnected;
|
|
1642
|
+
},
|
|
1643
|
+
get scenarioId() {
|
|
1644
|
+
return store.getSnapshot().scenarioId;
|
|
1645
|
+
},
|
|
1646
|
+
start,
|
|
1647
|
+
get partial() {
|
|
1648
|
+
return store.getSnapshot().partial;
|
|
1649
|
+
},
|
|
1650
|
+
get reconnect() {
|
|
1651
|
+
return store.getSnapshot().reconnect;
|
|
1652
|
+
},
|
|
1653
|
+
get sessionId() {
|
|
1654
|
+
return connection.getSessionId();
|
|
1655
|
+
},
|
|
1656
|
+
get status() {
|
|
1657
|
+
return store.getSnapshot().status;
|
|
1658
|
+
},
|
|
1659
|
+
get turns() {
|
|
1660
|
+
return store.getSnapshot().turns;
|
|
1661
|
+
},
|
|
1662
|
+
get assistantTexts() {
|
|
1663
|
+
return store.getSnapshot().assistantTexts;
|
|
1664
|
+
},
|
|
1665
|
+
get assistantAudio() {
|
|
1666
|
+
return store.getSnapshot().assistantAudio;
|
|
1667
|
+
},
|
|
1668
|
+
get call() {
|
|
1669
|
+
return store.getSnapshot().call;
|
|
1670
|
+
},
|
|
1671
|
+
sendAudio(audio) {
|
|
1672
|
+
connection.sendAudio(audio);
|
|
1673
|
+
},
|
|
1674
|
+
subscribe(subscriber) {
|
|
1675
|
+
subscribers.add(subscriber);
|
|
1676
|
+
return () => {
|
|
1677
|
+
subscribers.delete(subscriber);
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
};
|
|
1681
|
+
};
|
|
1682
|
+
|
|
1683
|
+
// src/angular/voice-stream.service.ts
|
|
1684
|
+
var _dec = [
|
|
1685
|
+
Injectable7({ providedIn: "root" })
|
|
1686
|
+
];
|
|
1687
|
+
var _init = __decoratorStart(undefined);
|
|
1688
|
+
|
|
1689
|
+
class VoiceStreamService {
|
|
1690
|
+
connect(path, options = {}) {
|
|
1691
|
+
const stream = createVoiceStream(path, options);
|
|
1692
|
+
const assistantAudioSignal = signal7([]);
|
|
1693
|
+
const assistantTextsSignal = signal7([]);
|
|
1694
|
+
const callSignal = signal7(null);
|
|
1695
|
+
const errorSignal = signal7(null);
|
|
1696
|
+
const isConnectedSignal = signal7(false);
|
|
1697
|
+
const partialSignal = signal7("");
|
|
1698
|
+
const reconnectSignal = signal7(stream.reconnect);
|
|
1699
|
+
const sessionIdSignal = signal7(stream.sessionId);
|
|
1700
|
+
const statusSignal = signal7(stream.status);
|
|
1701
|
+
const turnsSignal = signal7([]);
|
|
1702
|
+
const sync = () => {
|
|
1703
|
+
assistantAudioSignal.set([...stream.assistantAudio]);
|
|
1704
|
+
assistantTextsSignal.set([...stream.assistantTexts]);
|
|
1705
|
+
callSignal.set(stream.call);
|
|
1706
|
+
errorSignal.set(stream.error);
|
|
1707
|
+
isConnectedSignal.set(stream.isConnected);
|
|
1708
|
+
partialSignal.set(stream.partial);
|
|
1709
|
+
reconnectSignal.set(stream.reconnect);
|
|
1710
|
+
sessionIdSignal.set(stream.sessionId);
|
|
1711
|
+
statusSignal.set(stream.status);
|
|
1712
|
+
turnsSignal.set([...stream.turns]);
|
|
1713
|
+
};
|
|
1714
|
+
const unsubscribe = stream.subscribe(sync);
|
|
1715
|
+
sync();
|
|
1716
|
+
return {
|
|
1717
|
+
assistantAudio: computed7(() => assistantAudioSignal()),
|
|
1718
|
+
assistantTexts: computed7(() => assistantTextsSignal()),
|
|
1719
|
+
call: computed7(() => callSignal()),
|
|
1720
|
+
callControl: (message) => stream.callControl(message),
|
|
1721
|
+
close: () => {
|
|
1722
|
+
unsubscribe();
|
|
1723
|
+
stream.close();
|
|
1724
|
+
},
|
|
1725
|
+
endTurn: () => stream.endTurn(),
|
|
1726
|
+
error: computed7(() => errorSignal()),
|
|
1727
|
+
isConnected: computed7(() => isConnectedSignal()),
|
|
1728
|
+
partial: computed7(() => partialSignal()),
|
|
1729
|
+
reconnect: computed7(() => reconnectSignal()),
|
|
1730
|
+
sendAudio: (audio) => stream.sendAudio(audio),
|
|
1731
|
+
sessionId: computed7(() => sessionIdSignal()),
|
|
1732
|
+
status: computed7(() => statusSignal()),
|
|
1733
|
+
turns: computed7(() => turnsSignal())
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
VoiceStreamService = __decorateElement(_init, 0, "VoiceStreamService", _dec, VoiceStreamService);
|
|
1738
|
+
__runInitializers(_init, 1, VoiceStreamService);
|
|
1739
|
+
__decoratorMetadata(_init, VoiceStreamService);
|
|
1740
|
+
let _VoiceStreamService = VoiceStreamService;
|
|
1741
|
+
// src/angular/voice-controller.service.ts
|
|
1742
|
+
import { computed as computed8, Injectable as Injectable8, signal as signal8 } from "@angular/core";
|
|
1743
|
+
|
|
1744
|
+
// src/client/htmx.ts
|
|
1745
|
+
var DEFAULT_EVENT_NAME = "voice-refresh";
|
|
1746
|
+
var DEFAULT_QUERY_PARAM = "sessionId";
|
|
1747
|
+
var resolveElement = (input) => {
|
|
1748
|
+
if (typeof input !== "string") {
|
|
1749
|
+
return input;
|
|
1750
|
+
}
|
|
1751
|
+
return document.querySelector(input);
|
|
1752
|
+
};
|
|
1753
|
+
var buildRoute = (element, route, queryParam, sessionId) => {
|
|
1754
|
+
const baseRoute = route ?? element.getAttribute("hx-get") ?? "";
|
|
1755
|
+
if (!baseRoute) {
|
|
1756
|
+
return "";
|
|
1757
|
+
}
|
|
1758
|
+
const url = new URL(baseRoute, window.location.origin);
|
|
1759
|
+
if (sessionId) {
|
|
1760
|
+
url.searchParams.set(queryParam, sessionId);
|
|
1761
|
+
} else {
|
|
1762
|
+
url.searchParams.delete(queryParam);
|
|
1763
|
+
}
|
|
1764
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
1765
|
+
};
|
|
1766
|
+
var bindVoiceHTMX = (stream, options) => {
|
|
1767
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
1768
|
+
return () => {};
|
|
1769
|
+
}
|
|
1770
|
+
const element = resolveElement(options.element);
|
|
1771
|
+
if (!element) {
|
|
1772
|
+
return () => {};
|
|
1773
|
+
}
|
|
1774
|
+
const eventName = options.eventName ?? DEFAULT_EVENT_NAME;
|
|
1775
|
+
const queryParam = options.sessionQueryParam ?? DEFAULT_QUERY_PARAM;
|
|
1776
|
+
const sync = () => {
|
|
1777
|
+
const htmxWindow = window;
|
|
1778
|
+
const nextRoute = buildRoute(element, options.route, queryParam, stream.sessionId);
|
|
1779
|
+
if (nextRoute) {
|
|
1780
|
+
element.setAttribute("hx-get", nextRoute);
|
|
1781
|
+
}
|
|
1782
|
+
htmxWindow.htmx?.process?.(element);
|
|
1783
|
+
htmxWindow.htmx?.trigger?.(element, eventName);
|
|
1784
|
+
};
|
|
1785
|
+
const unsubscribe = stream.subscribe(sync);
|
|
1786
|
+
sync();
|
|
1787
|
+
return () => {
|
|
1788
|
+
unsubscribe();
|
|
1789
|
+
};
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1792
|
+
// src/client/microphone.ts
|
|
1793
|
+
var clampSample = (value) => Math.max(-1, Math.min(1, value));
|
|
1794
|
+
var floatTo16BitPCM = (input) => {
|
|
1795
|
+
const output = new Int16Array(input.length);
|
|
1796
|
+
for (let index = 0;index < input.length; index += 1) {
|
|
1797
|
+
const sample = clampSample(input[index] ?? 0);
|
|
1798
|
+
output[index] = sample < 0 ? sample * 32768 : sample * 32767;
|
|
1799
|
+
}
|
|
1800
|
+
return new Uint8Array(output.buffer);
|
|
1801
|
+
};
|
|
1802
|
+
var getPcmLevel = (audio) => {
|
|
1803
|
+
const bytes = audio instanceof Uint8Array ? audio : new Uint8Array(audio);
|
|
1804
|
+
if (bytes.byteLength < 2) {
|
|
1805
|
+
return 0;
|
|
1806
|
+
}
|
|
1807
|
+
const samples = new Int16Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 2));
|
|
1808
|
+
if (samples.length === 0) {
|
|
1809
|
+
return 0;
|
|
1810
|
+
}
|
|
1811
|
+
let sumSquares = 0;
|
|
1812
|
+
for (const sample of samples) {
|
|
1813
|
+
const normalized = sample / 32768;
|
|
1814
|
+
sumSquares += normalized * normalized;
|
|
1815
|
+
}
|
|
1816
|
+
return Math.min(1, Math.max(0, Math.sqrt(sumSquares / samples.length) * 5.5));
|
|
1817
|
+
};
|
|
1818
|
+
var downsampleBuffer = (input, sourceRate, targetRate) => {
|
|
1819
|
+
if (sourceRate === targetRate) {
|
|
1820
|
+
return input;
|
|
1821
|
+
}
|
|
1822
|
+
const ratio = sourceRate / targetRate;
|
|
1823
|
+
const length = Math.round(input.length / ratio);
|
|
1824
|
+
const output = new Float32Array(length);
|
|
1825
|
+
let offsetResult = 0;
|
|
1826
|
+
let offsetBuffer = 0;
|
|
1827
|
+
while (offsetResult < output.length) {
|
|
1828
|
+
const nextOffsetBuffer = Math.round((offsetResult + 1) * ratio);
|
|
1829
|
+
let accum = 0;
|
|
1830
|
+
let count = 0;
|
|
1831
|
+
for (let index = offsetBuffer;index < nextOffsetBuffer && index < input.length; index += 1) {
|
|
1832
|
+
accum += input[index] ?? 0;
|
|
1833
|
+
count += 1;
|
|
1834
|
+
}
|
|
1835
|
+
output[offsetResult] = count > 0 ? accum / count : 0;
|
|
1836
|
+
offsetResult += 1;
|
|
1837
|
+
offsetBuffer = nextOffsetBuffer;
|
|
1838
|
+
}
|
|
1839
|
+
return output;
|
|
1840
|
+
};
|
|
1841
|
+
var createMicrophoneCapture = (options) => {
|
|
1842
|
+
let audioContext = null;
|
|
1843
|
+
let sourceNode = null;
|
|
1844
|
+
let processorNode = null;
|
|
1845
|
+
let mediaStream = null;
|
|
1846
|
+
const start = async () => {
|
|
1847
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
1848
|
+
throw new Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");
|
|
1849
|
+
}
|
|
1850
|
+
const AudioContextCtor = (typeof window !== "undefined" ? window.AudioContext ?? window.webkitAudioContext : undefined) ?? AudioContext;
|
|
1851
|
+
if (!AudioContextCtor) {
|
|
1852
|
+
throw new Error("Browser microphone capture requires AudioContext support.");
|
|
1853
|
+
}
|
|
1854
|
+
mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
1855
|
+
audio: {
|
|
1856
|
+
channelCount: options.channelCount ?? 1
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1859
|
+
audioContext = new AudioContextCtor;
|
|
1860
|
+
sourceNode = audioContext.createMediaStreamSource(mediaStream);
|
|
1861
|
+
processorNode = audioContext.createScriptProcessor(4096, 1, 1);
|
|
1862
|
+
processorNode.onaudioprocess = (event) => {
|
|
1863
|
+
const channel = event.inputBuffer.getChannelData(0);
|
|
1864
|
+
const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
|
|
1865
|
+
const pcm = floatTo16BitPCM(downsampled);
|
|
1866
|
+
options.onLevel?.(getPcmLevel(pcm));
|
|
1867
|
+
options.onAudio(pcm);
|
|
1868
|
+
};
|
|
1869
|
+
sourceNode.connect(processorNode);
|
|
1870
|
+
processorNode.connect(audioContext.destination);
|
|
1871
|
+
};
|
|
1872
|
+
const stop = () => {
|
|
1873
|
+
processorNode?.disconnect();
|
|
1874
|
+
sourceNode?.disconnect();
|
|
1875
|
+
mediaStream?.getTracks().forEach((track) => track.stop());
|
|
1876
|
+
audioContext?.close();
|
|
1877
|
+
options.onLevel?.(0);
|
|
1878
|
+
audioContext = null;
|
|
1879
|
+
mediaStream = null;
|
|
1880
|
+
processorNode = null;
|
|
1881
|
+
sourceNode = null;
|
|
1882
|
+
};
|
|
1883
|
+
return { start, stop };
|
|
1884
|
+
};
|
|
1885
|
+
|
|
1886
|
+
// src/audioConditioning.ts
|
|
1887
|
+
var DEFAULT_TARGET_LEVEL = 0.08;
|
|
1888
|
+
var DEFAULT_MAX_GAIN = 3;
|
|
1889
|
+
var DEFAULT_NOISE_GATE_THRESHOLD = 0.006;
|
|
1890
|
+
var DEFAULT_NOISE_GATE_ATTENUATION = 0.15;
|
|
1891
|
+
var toInt16Array = (audio) => {
|
|
1892
|
+
if (audio instanceof ArrayBuffer) {
|
|
1893
|
+
return new Int16Array(audio, 0, Math.floor(audio.byteLength / 2));
|
|
1894
|
+
}
|
|
1895
|
+
return new Int16Array(audio.buffer, audio.byteOffset, Math.floor(audio.byteLength / 2));
|
|
1896
|
+
};
|
|
1897
|
+
var computeRms = (samples) => {
|
|
1898
|
+
if (samples.length === 0) {
|
|
1899
|
+
return 0;
|
|
1900
|
+
}
|
|
1901
|
+
let sumSquares = 0;
|
|
1902
|
+
for (const sample of samples) {
|
|
1903
|
+
const normalized = sample / 32768;
|
|
1904
|
+
sumSquares += normalized * normalized;
|
|
1905
|
+
}
|
|
1906
|
+
return Math.sqrt(sumSquares / samples.length);
|
|
1907
|
+
};
|
|
1908
|
+
var resolveAudioConditioningConfig = (config) => {
|
|
1909
|
+
if (!config || config.enabled === false) {
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
return {
|
|
1913
|
+
enabled: true,
|
|
776
1914
|
maxGain: config.maxGain ?? DEFAULT_MAX_GAIN,
|
|
777
1915
|
noiseGateAttenuation: config.noiseGateAttenuation ?? DEFAULT_NOISE_GATE_ATTENUATION,
|
|
778
1916
|
noiseGateThreshold: config.noiseGateThreshold ?? DEFAULT_NOISE_GATE_THRESHOLD,
|
|
779
1917
|
targetLevel: config.targetLevel ?? DEFAULT_TARGET_LEVEL
|
|
780
1918
|
};
|
|
781
1919
|
};
|
|
782
|
-
var conditionAudioChunk = (audio, config) => {
|
|
783
|
-
if (!config) {
|
|
784
|
-
return audio;
|
|
1920
|
+
var conditionAudioChunk = (audio, config) => {
|
|
1921
|
+
if (!config) {
|
|
1922
|
+
return audio;
|
|
1923
|
+
}
|
|
1924
|
+
const source = toInt16Array(audio);
|
|
1925
|
+
if (source.length === 0) {
|
|
1926
|
+
return audio;
|
|
1927
|
+
}
|
|
1928
|
+
const rms = computeRms(source);
|
|
1929
|
+
const output = new Int16Array(source.length);
|
|
1930
|
+
const gateFactor = rms < config.noiseGateThreshold ? config.noiseGateAttenuation : 1;
|
|
1931
|
+
const baseLevel = Math.max(rms * gateFactor, 0.000001);
|
|
1932
|
+
const gain = Math.min(config.maxGain, config.targetLevel / baseLevel);
|
|
1933
|
+
const appliedGain = Math.max(0.25, gain) * gateFactor;
|
|
1934
|
+
for (let index = 0;index < source.length; index += 1) {
|
|
1935
|
+
const next = Math.round(source[index] * appliedGain);
|
|
1936
|
+
output[index] = Math.max(-32768, Math.min(32767, next));
|
|
1937
|
+
}
|
|
1938
|
+
return new Uint8Array(output.buffer);
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
// src/turnProfiles.ts
|
|
1942
|
+
var TURN_PROFILE_DEFAULTS = {
|
|
1943
|
+
balanced: {
|
|
1944
|
+
qualityProfile: "general",
|
|
1945
|
+
silenceMs: 1400,
|
|
1946
|
+
speechThreshold: 0.012,
|
|
1947
|
+
transcriptStabilityMs: 1000
|
|
1948
|
+
},
|
|
1949
|
+
fast: {
|
|
1950
|
+
qualityProfile: "general",
|
|
1951
|
+
silenceMs: 700,
|
|
1952
|
+
speechThreshold: 0.015,
|
|
1953
|
+
transcriptStabilityMs: 450
|
|
1954
|
+
},
|
|
1955
|
+
"long-form": {
|
|
1956
|
+
qualityProfile: "general",
|
|
1957
|
+
silenceMs: 2200,
|
|
1958
|
+
speechThreshold: 0.01,
|
|
1959
|
+
transcriptStabilityMs: 1500
|
|
1960
|
+
}
|
|
1961
|
+
};
|
|
1962
|
+
var QUALITY_PROFILE_DEFAULTS = {
|
|
1963
|
+
general: {},
|
|
1964
|
+
"accent-heavy": {
|
|
1965
|
+
silenceMs: 1200,
|
|
1966
|
+
speechThreshold: 0.01,
|
|
1967
|
+
transcriptStabilityMs: 1200
|
|
1968
|
+
},
|
|
1969
|
+
"noisy-room": {
|
|
1970
|
+
silenceMs: 2000,
|
|
1971
|
+
speechThreshold: 0.02,
|
|
1972
|
+
transcriptStabilityMs: 1600
|
|
1973
|
+
},
|
|
1974
|
+
"short-command": {
|
|
1975
|
+
silenceMs: 500,
|
|
1976
|
+
speechThreshold: 0.016,
|
|
1977
|
+
transcriptStabilityMs: 420
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
var DEFAULT_TURN_PROFILE = "fast";
|
|
1981
|
+
var DEFAULT_QUALITY_PROFILE = "general";
|
|
1982
|
+
var resolveTurnDetectionConfig = (config) => {
|
|
1983
|
+
const profile = config?.profile ?? DEFAULT_TURN_PROFILE;
|
|
1984
|
+
const qualityProfile = config?.qualityProfile ?? DEFAULT_QUALITY_PROFILE;
|
|
1985
|
+
const preset = TURN_PROFILE_DEFAULTS[profile];
|
|
1986
|
+
const quality = QUALITY_PROFILE_DEFAULTS[qualityProfile];
|
|
1987
|
+
return {
|
|
1988
|
+
profile,
|
|
1989
|
+
qualityProfile,
|
|
1990
|
+
silenceMs: config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs,
|
|
1991
|
+
speechThreshold: config?.speechThreshold ?? quality.speechThreshold ?? preset.speechThreshold,
|
|
1992
|
+
transcriptStabilityMs: config?.transcriptStabilityMs ?? quality.transcriptStabilityMs ?? preset.transcriptStabilityMs
|
|
1993
|
+
};
|
|
1994
|
+
};
|
|
1995
|
+
|
|
1996
|
+
// src/presets.ts
|
|
1997
|
+
var PRESET_INPUTS = {
|
|
1998
|
+
chat: {
|
|
1999
|
+
audioConditioning: {
|
|
2000
|
+
enabled: true,
|
|
2001
|
+
maxGain: 2.5,
|
|
2002
|
+
noiseGateAttenuation: 0,
|
|
2003
|
+
noiseGateThreshold: 0.004,
|
|
2004
|
+
targetLevel: 0.08
|
|
2005
|
+
},
|
|
2006
|
+
capture: {
|
|
2007
|
+
channelCount: 1,
|
|
2008
|
+
sampleRateHz: 16000
|
|
2009
|
+
},
|
|
2010
|
+
connection: {
|
|
2011
|
+
maxReconnectAttempts: 10,
|
|
2012
|
+
pingInterval: 30000,
|
|
2013
|
+
reconnect: true
|
|
2014
|
+
},
|
|
2015
|
+
sttLifecycle: "continuous",
|
|
2016
|
+
turnDetection: {
|
|
2017
|
+
qualityProfile: "short-command",
|
|
2018
|
+
profile: "balanced"
|
|
2019
|
+
}
|
|
2020
|
+
},
|
|
2021
|
+
default: {
|
|
2022
|
+
capture: {
|
|
2023
|
+
channelCount: 1,
|
|
2024
|
+
sampleRateHz: 16000
|
|
2025
|
+
},
|
|
2026
|
+
connection: {
|
|
2027
|
+
maxReconnectAttempts: 10,
|
|
2028
|
+
pingInterval: 30000,
|
|
2029
|
+
reconnect: true
|
|
2030
|
+
},
|
|
2031
|
+
sttLifecycle: "continuous",
|
|
2032
|
+
turnDetection: {
|
|
2033
|
+
qualityProfile: "general",
|
|
2034
|
+
profile: "fast"
|
|
2035
|
+
}
|
|
2036
|
+
},
|
|
2037
|
+
dictation: {
|
|
2038
|
+
audioConditioning: {
|
|
2039
|
+
enabled: true,
|
|
2040
|
+
maxGain: 2.25,
|
|
2041
|
+
noiseGateAttenuation: 0.05,
|
|
2042
|
+
noiseGateThreshold: 0.003,
|
|
2043
|
+
targetLevel: 0.08
|
|
2044
|
+
},
|
|
2045
|
+
capture: {
|
|
2046
|
+
channelCount: 1,
|
|
2047
|
+
sampleRateHz: 16000
|
|
2048
|
+
},
|
|
2049
|
+
connection: {
|
|
2050
|
+
maxReconnectAttempts: 12,
|
|
2051
|
+
pingInterval: 30000,
|
|
2052
|
+
reconnect: true
|
|
2053
|
+
},
|
|
2054
|
+
sttLifecycle: "continuous",
|
|
2055
|
+
turnDetection: {
|
|
2056
|
+
qualityProfile: "accent-heavy",
|
|
2057
|
+
profile: "long-form"
|
|
2058
|
+
}
|
|
2059
|
+
},
|
|
2060
|
+
"guided-intake": {
|
|
2061
|
+
audioConditioning: {
|
|
2062
|
+
enabled: true,
|
|
2063
|
+
maxGain: 2.5,
|
|
2064
|
+
noiseGateAttenuation: 0,
|
|
2065
|
+
noiseGateThreshold: 0.004,
|
|
2066
|
+
targetLevel: 0.08
|
|
2067
|
+
},
|
|
2068
|
+
capture: {
|
|
2069
|
+
channelCount: 1,
|
|
2070
|
+
sampleRateHz: 16000
|
|
2071
|
+
},
|
|
2072
|
+
connection: {
|
|
2073
|
+
maxReconnectAttempts: 12,
|
|
2074
|
+
pingInterval: 30000,
|
|
2075
|
+
reconnect: true
|
|
2076
|
+
},
|
|
2077
|
+
sttLifecycle: "turn-scoped",
|
|
2078
|
+
turnDetection: {
|
|
2079
|
+
qualityProfile: "accent-heavy",
|
|
2080
|
+
profile: "long-form"
|
|
2081
|
+
}
|
|
2082
|
+
},
|
|
2083
|
+
"noisy-room": {
|
|
2084
|
+
audioConditioning: {
|
|
2085
|
+
enabled: true,
|
|
2086
|
+
maxGain: 3,
|
|
2087
|
+
noiseGateAttenuation: 0.12,
|
|
2088
|
+
noiseGateThreshold: 0.006,
|
|
2089
|
+
targetLevel: 0.085
|
|
2090
|
+
},
|
|
2091
|
+
capture: {
|
|
2092
|
+
channelCount: 1,
|
|
2093
|
+
sampleRateHz: 16000
|
|
2094
|
+
},
|
|
2095
|
+
connection: {
|
|
2096
|
+
maxReconnectAttempts: 14,
|
|
2097
|
+
pingInterval: 45000,
|
|
2098
|
+
reconnect: true
|
|
2099
|
+
},
|
|
2100
|
+
sttLifecycle: "continuous",
|
|
2101
|
+
turnDetection: {
|
|
2102
|
+
qualityProfile: "noisy-room",
|
|
2103
|
+
profile: "long-form",
|
|
2104
|
+
silenceMs: 2100,
|
|
2105
|
+
speechThreshold: 0.02,
|
|
2106
|
+
transcriptStabilityMs: 1650
|
|
2107
|
+
}
|
|
2108
|
+
},
|
|
2109
|
+
"pstn-balanced": {
|
|
2110
|
+
audioConditioning: {
|
|
2111
|
+
enabled: true,
|
|
2112
|
+
maxGain: 2.8,
|
|
2113
|
+
noiseGateAttenuation: 0.07,
|
|
2114
|
+
noiseGateThreshold: 0.005,
|
|
2115
|
+
targetLevel: 0.08
|
|
2116
|
+
},
|
|
2117
|
+
capture: {
|
|
2118
|
+
channelCount: 1,
|
|
2119
|
+
sampleRateHz: 16000
|
|
2120
|
+
},
|
|
2121
|
+
connection: {
|
|
2122
|
+
maxReconnectAttempts: 14,
|
|
2123
|
+
pingInterval: 45000,
|
|
2124
|
+
reconnect: true
|
|
2125
|
+
},
|
|
2126
|
+
sttLifecycle: "continuous",
|
|
2127
|
+
turnDetection: {
|
|
2128
|
+
qualityProfile: "noisy-room",
|
|
2129
|
+
profile: "long-form",
|
|
2130
|
+
silenceMs: 660,
|
|
2131
|
+
speechThreshold: 0.012,
|
|
2132
|
+
transcriptStabilityMs: 300
|
|
2133
|
+
}
|
|
2134
|
+
},
|
|
2135
|
+
"pstn-fast": {
|
|
2136
|
+
audioConditioning: {
|
|
2137
|
+
enabled: true,
|
|
2138
|
+
maxGain: 2.75,
|
|
2139
|
+
noiseGateAttenuation: 0.06,
|
|
2140
|
+
noiseGateThreshold: 0.005,
|
|
2141
|
+
targetLevel: 0.08
|
|
2142
|
+
},
|
|
2143
|
+
capture: {
|
|
2144
|
+
channelCount: 1,
|
|
2145
|
+
sampleRateHz: 16000
|
|
2146
|
+
},
|
|
2147
|
+
connection: {
|
|
2148
|
+
maxReconnectAttempts: 14,
|
|
2149
|
+
pingInterval: 45000,
|
|
2150
|
+
reconnect: true
|
|
2151
|
+
},
|
|
2152
|
+
sttLifecycle: "continuous",
|
|
2153
|
+
turnDetection: {
|
|
2154
|
+
qualityProfile: "noisy-room",
|
|
2155
|
+
profile: "long-form",
|
|
2156
|
+
silenceMs: 620,
|
|
2157
|
+
speechThreshold: 0.012,
|
|
2158
|
+
transcriptStabilityMs: 280
|
|
2159
|
+
}
|
|
2160
|
+
},
|
|
2161
|
+
reliability: {
|
|
2162
|
+
audioConditioning: {
|
|
2163
|
+
enabled: true,
|
|
2164
|
+
maxGain: 2.9,
|
|
2165
|
+
noiseGateAttenuation: 0.08,
|
|
2166
|
+
noiseGateThreshold: 0.005,
|
|
2167
|
+
targetLevel: 0.08
|
|
2168
|
+
},
|
|
2169
|
+
capture: {
|
|
2170
|
+
channelCount: 1,
|
|
2171
|
+
sampleRateHz: 16000
|
|
2172
|
+
},
|
|
2173
|
+
connection: {
|
|
2174
|
+
maxReconnectAttempts: 14,
|
|
2175
|
+
pingInterval: 45000,
|
|
2176
|
+
reconnect: true
|
|
2177
|
+
},
|
|
2178
|
+
sttLifecycle: "continuous",
|
|
2179
|
+
turnDetection: {
|
|
2180
|
+
qualityProfile: "noisy-room",
|
|
2181
|
+
profile: "long-form"
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
var resolveVoiceRuntimePreset = (name = "default") => {
|
|
2186
|
+
const preset = PRESET_INPUTS[name];
|
|
2187
|
+
return {
|
|
2188
|
+
audioConditioning: resolveAudioConditioningConfig(preset.audioConditioning),
|
|
2189
|
+
capture: {
|
|
2190
|
+
channelCount: preset.capture?.channelCount ?? 1,
|
|
2191
|
+
sampleRateHz: preset.capture?.sampleRateHz ?? 16000
|
|
2192
|
+
},
|
|
2193
|
+
connection: {
|
|
2194
|
+
...preset.connection
|
|
2195
|
+
},
|
|
2196
|
+
name,
|
|
2197
|
+
sttLifecycle: preset.sttLifecycle ?? "continuous",
|
|
2198
|
+
turnDetection: resolveTurnDetectionConfig(preset.turnDetection)
|
|
2199
|
+
};
|
|
2200
|
+
};
|
|
2201
|
+
|
|
2202
|
+
// src/client/controller.ts
|
|
2203
|
+
var createInitialState2 = (stream) => ({
|
|
2204
|
+
assistantAudio: [...stream.assistantAudio],
|
|
2205
|
+
assistantTexts: [...stream.assistantTexts],
|
|
2206
|
+
call: stream.call,
|
|
2207
|
+
error: stream.error,
|
|
2208
|
+
isConnected: stream.isConnected,
|
|
2209
|
+
isRecording: false,
|
|
2210
|
+
partial: stream.partial,
|
|
2211
|
+
reconnect: stream.reconnect,
|
|
2212
|
+
recordingError: null,
|
|
2213
|
+
sessionId: stream.sessionId,
|
|
2214
|
+
scenarioId: stream.scenarioId,
|
|
2215
|
+
status: stream.status,
|
|
2216
|
+
turns: [...stream.turns]
|
|
2217
|
+
});
|
|
2218
|
+
var createVoiceController = (path, options = {}) => {
|
|
2219
|
+
const preset = resolveVoiceRuntimePreset(options.preset);
|
|
2220
|
+
const stream = createVoiceStream(path, {
|
|
2221
|
+
...preset.connection,
|
|
2222
|
+
...options.connection
|
|
2223
|
+
});
|
|
2224
|
+
let capture = null;
|
|
2225
|
+
let state = createInitialState2(stream);
|
|
2226
|
+
const subscribers = new Set;
|
|
2227
|
+
const notify = () => {
|
|
2228
|
+
for (const subscriber of subscribers) {
|
|
2229
|
+
subscriber();
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
const sync = () => {
|
|
2233
|
+
state = {
|
|
2234
|
+
...state,
|
|
2235
|
+
assistantAudio: [...stream.assistantAudio],
|
|
2236
|
+
assistantTexts: [...stream.assistantTexts],
|
|
2237
|
+
call: stream.call,
|
|
2238
|
+
error: stream.error,
|
|
2239
|
+
isConnected: stream.isConnected,
|
|
2240
|
+
partial: stream.partial,
|
|
2241
|
+
reconnect: stream.reconnect,
|
|
2242
|
+
sessionId: stream.sessionId,
|
|
2243
|
+
scenarioId: stream.scenarioId,
|
|
2244
|
+
status: stream.status,
|
|
2245
|
+
turns: [...stream.turns]
|
|
2246
|
+
};
|
|
2247
|
+
if (options.autoStopOnComplete !== false && state.status === "completed" && state.isRecording) {
|
|
2248
|
+
capture?.stop();
|
|
2249
|
+
capture = null;
|
|
2250
|
+
state = {
|
|
2251
|
+
...state,
|
|
2252
|
+
isRecording: false
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
notify();
|
|
2256
|
+
};
|
|
2257
|
+
const unsubscribeStream = stream.subscribe(sync);
|
|
2258
|
+
sync();
|
|
2259
|
+
const ensureCapture = () => {
|
|
2260
|
+
if (capture) {
|
|
2261
|
+
return capture;
|
|
2262
|
+
}
|
|
2263
|
+
capture = createMicrophoneCapture({
|
|
2264
|
+
channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
|
|
2265
|
+
onLevel: options.capture?.onLevel,
|
|
2266
|
+
onAudio: (audio) => {
|
|
2267
|
+
if (options.capture?.onAudio) {
|
|
2268
|
+
options.capture.onAudio(audio, stream.sendAudio);
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
stream.sendAudio(audio);
|
|
2272
|
+
},
|
|
2273
|
+
sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
|
|
2274
|
+
});
|
|
2275
|
+
return capture;
|
|
2276
|
+
};
|
|
2277
|
+
const stopRecording = () => {
|
|
2278
|
+
capture?.stop();
|
|
2279
|
+
capture = null;
|
|
2280
|
+
state = {
|
|
2281
|
+
...state,
|
|
2282
|
+
isRecording: false
|
|
2283
|
+
};
|
|
2284
|
+
notify();
|
|
2285
|
+
};
|
|
2286
|
+
const startRecording = async () => {
|
|
2287
|
+
if (state.isRecording) {
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
try {
|
|
2291
|
+
state = {
|
|
2292
|
+
...state,
|
|
2293
|
+
recordingError: null
|
|
2294
|
+
};
|
|
2295
|
+
notify();
|
|
2296
|
+
await ensureCapture().start();
|
|
2297
|
+
state = {
|
|
2298
|
+
...state,
|
|
2299
|
+
isRecording: true
|
|
2300
|
+
};
|
|
2301
|
+
notify();
|
|
2302
|
+
} catch (error) {
|
|
2303
|
+
capture = null;
|
|
2304
|
+
state = {
|
|
2305
|
+
...state,
|
|
2306
|
+
isRecording: false,
|
|
2307
|
+
recordingError: error instanceof Error ? error.message : String(error)
|
|
2308
|
+
};
|
|
2309
|
+
notify();
|
|
2310
|
+
throw error;
|
|
2311
|
+
}
|
|
2312
|
+
};
|
|
2313
|
+
const close = () => {
|
|
2314
|
+
unsubscribeStream();
|
|
2315
|
+
stopRecording();
|
|
2316
|
+
stream.close();
|
|
2317
|
+
};
|
|
2318
|
+
return {
|
|
2319
|
+
bindHTMX(bindingOptions) {
|
|
2320
|
+
return bindVoiceHTMX(stream, bindingOptions);
|
|
2321
|
+
},
|
|
2322
|
+
callControl: (message) => stream.callControl(message),
|
|
2323
|
+
close,
|
|
2324
|
+
endTurn: () => stream.endTurn(),
|
|
2325
|
+
get error() {
|
|
2326
|
+
return state.error;
|
|
2327
|
+
},
|
|
2328
|
+
getServerSnapshot: () => state,
|
|
2329
|
+
getSnapshot: () => state,
|
|
2330
|
+
get isConnected() {
|
|
2331
|
+
return state.isConnected;
|
|
2332
|
+
},
|
|
2333
|
+
get isRecording() {
|
|
2334
|
+
return state.isRecording;
|
|
2335
|
+
},
|
|
2336
|
+
get partial() {
|
|
2337
|
+
return state.partial;
|
|
2338
|
+
},
|
|
2339
|
+
get recordingError() {
|
|
2340
|
+
return state.recordingError;
|
|
2341
|
+
},
|
|
2342
|
+
get reconnect() {
|
|
2343
|
+
return state.reconnect;
|
|
2344
|
+
},
|
|
2345
|
+
sendAudio: (audio) => stream.sendAudio(audio),
|
|
2346
|
+
get sessionId() {
|
|
2347
|
+
return state.sessionId;
|
|
2348
|
+
},
|
|
2349
|
+
get scenarioId() {
|
|
2350
|
+
return state.scenarioId;
|
|
2351
|
+
},
|
|
2352
|
+
startRecording,
|
|
2353
|
+
get status() {
|
|
2354
|
+
return state.status;
|
|
2355
|
+
},
|
|
2356
|
+
stopRecording,
|
|
2357
|
+
subscribe: (subscriber) => {
|
|
2358
|
+
subscribers.add(subscriber);
|
|
2359
|
+
return () => {
|
|
2360
|
+
subscribers.delete(subscriber);
|
|
2361
|
+
};
|
|
2362
|
+
},
|
|
2363
|
+
toggleRecording: async () => {
|
|
2364
|
+
if (state.isRecording) {
|
|
2365
|
+
stopRecording();
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
await startRecording();
|
|
2369
|
+
},
|
|
2370
|
+
get turns() {
|
|
2371
|
+
return state.turns;
|
|
2372
|
+
},
|
|
2373
|
+
get assistantTexts() {
|
|
2374
|
+
return state.assistantTexts;
|
|
2375
|
+
},
|
|
2376
|
+
get assistantAudio() {
|
|
2377
|
+
return state.assistantAudio;
|
|
2378
|
+
},
|
|
2379
|
+
get call() {
|
|
2380
|
+
return state.call;
|
|
2381
|
+
}
|
|
2382
|
+
};
|
|
2383
|
+
};
|
|
2384
|
+
|
|
2385
|
+
// src/angular/voice-controller.service.ts
|
|
2386
|
+
var _dec = [
|
|
2387
|
+
Injectable8({ providedIn: "root" })
|
|
2388
|
+
];
|
|
2389
|
+
var _init = __decoratorStart(undefined);
|
|
2390
|
+
|
|
2391
|
+
class VoiceControllerService {
|
|
2392
|
+
connect(path, options = {}) {
|
|
2393
|
+
const controller = createVoiceController(path, options);
|
|
2394
|
+
const assistantAudioSignal = signal8([]);
|
|
2395
|
+
const assistantTextsSignal = signal8([]);
|
|
2396
|
+
const errorSignal = signal8(null);
|
|
2397
|
+
const isConnectedSignal = signal8(false);
|
|
2398
|
+
const isRecordingSignal = signal8(false);
|
|
2399
|
+
const partialSignal = signal8("");
|
|
2400
|
+
const reconnectSignal = signal8(controller.reconnect);
|
|
2401
|
+
const recordingErrorSignal = signal8(null);
|
|
2402
|
+
const sessionIdSignal = signal8(controller.sessionId);
|
|
2403
|
+
const statusSignal = signal8(controller.status);
|
|
2404
|
+
const turnsSignal = signal8([]);
|
|
2405
|
+
const sync = () => {
|
|
2406
|
+
assistantAudioSignal.set([...controller.assistantAudio]);
|
|
2407
|
+
assistantTextsSignal.set([...controller.assistantTexts]);
|
|
2408
|
+
errorSignal.set(controller.error);
|
|
2409
|
+
isConnectedSignal.set(controller.isConnected);
|
|
2410
|
+
isRecordingSignal.set(controller.isRecording);
|
|
2411
|
+
partialSignal.set(controller.partial);
|
|
2412
|
+
reconnectSignal.set(controller.reconnect);
|
|
2413
|
+
recordingErrorSignal.set(controller.recordingError);
|
|
2414
|
+
sessionIdSignal.set(controller.sessionId);
|
|
2415
|
+
statusSignal.set(controller.status);
|
|
2416
|
+
turnsSignal.set([...controller.turns]);
|
|
2417
|
+
};
|
|
2418
|
+
const unsubscribe = controller.subscribe(sync);
|
|
2419
|
+
sync();
|
|
2420
|
+
return {
|
|
2421
|
+
assistantAudio: computed8(() => assistantAudioSignal()),
|
|
2422
|
+
assistantTexts: computed8(() => assistantTextsSignal()),
|
|
2423
|
+
bindHTMX: controller.bindHTMX,
|
|
2424
|
+
close: () => {
|
|
2425
|
+
unsubscribe();
|
|
2426
|
+
controller.close();
|
|
2427
|
+
},
|
|
2428
|
+
endTurn: () => controller.endTurn(),
|
|
2429
|
+
error: computed8(() => errorSignal()),
|
|
2430
|
+
isConnected: computed8(() => isConnectedSignal()),
|
|
2431
|
+
isRecording: computed8(() => isRecordingSignal()),
|
|
2432
|
+
partial: computed8(() => partialSignal()),
|
|
2433
|
+
reconnect: computed8(() => reconnectSignal()),
|
|
2434
|
+
recordingError: computed8(() => recordingErrorSignal()),
|
|
2435
|
+
sendAudio: (audio) => controller.sendAudio(audio),
|
|
2436
|
+
sessionId: computed8(() => sessionIdSignal()),
|
|
2437
|
+
startRecording: () => controller.startRecording(),
|
|
2438
|
+
status: computed8(() => statusSignal()),
|
|
2439
|
+
stopRecording: () => controller.stopRecording(),
|
|
2440
|
+
toggleRecording: () => controller.toggleRecording(),
|
|
2441
|
+
turns: computed8(() => turnsSignal())
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
VoiceControllerService = __decorateElement(_init, 0, "VoiceControllerService", _dec, VoiceControllerService);
|
|
2446
|
+
__runInitializers(_init, 1, VoiceControllerService);
|
|
2447
|
+
__decoratorMetadata(_init, VoiceControllerService);
|
|
2448
|
+
let _VoiceControllerService = VoiceControllerService;
|
|
2449
|
+
// src/angular/voice-provider-capabilities.service.ts
|
|
2450
|
+
import { computed as computed9, Injectable as Injectable9, signal as signal9 } from "@angular/core";
|
|
2451
|
+
|
|
2452
|
+
// src/client/providerCapabilities.ts
|
|
2453
|
+
var fetchVoiceProviderCapabilities = async (path = "/api/provider-capabilities", options = {}) => {
|
|
2454
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2455
|
+
const response = await fetchImpl(path);
|
|
2456
|
+
if (!response.ok) {
|
|
2457
|
+
throw new Error(`Voice provider capabilities failed: HTTP ${response.status}`);
|
|
2458
|
+
}
|
|
2459
|
+
return await response.json();
|
|
2460
|
+
};
|
|
2461
|
+
var createVoiceProviderCapabilitiesStore = (path = "/api/provider-capabilities", options = {}) => {
|
|
2462
|
+
const listeners = new Set;
|
|
2463
|
+
let closed = false;
|
|
2464
|
+
let timer;
|
|
2465
|
+
let snapshot = {
|
|
2466
|
+
error: null,
|
|
2467
|
+
isLoading: false
|
|
2468
|
+
};
|
|
2469
|
+
const emit = () => {
|
|
2470
|
+
for (const listener of listeners) {
|
|
2471
|
+
listener();
|
|
2472
|
+
}
|
|
2473
|
+
};
|
|
2474
|
+
const refresh = async () => {
|
|
2475
|
+
if (closed) {
|
|
2476
|
+
return snapshot.report;
|
|
2477
|
+
}
|
|
2478
|
+
snapshot = {
|
|
2479
|
+
...snapshot,
|
|
2480
|
+
error: null,
|
|
2481
|
+
isLoading: true
|
|
2482
|
+
};
|
|
2483
|
+
emit();
|
|
2484
|
+
try {
|
|
2485
|
+
const report = await fetchVoiceProviderCapabilities(path, options);
|
|
2486
|
+
snapshot = {
|
|
2487
|
+
error: null,
|
|
2488
|
+
isLoading: false,
|
|
2489
|
+
report,
|
|
2490
|
+
updatedAt: Date.now()
|
|
2491
|
+
};
|
|
2492
|
+
emit();
|
|
2493
|
+
return report;
|
|
2494
|
+
} catch (error) {
|
|
2495
|
+
snapshot = {
|
|
2496
|
+
...snapshot,
|
|
2497
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2498
|
+
isLoading: false
|
|
2499
|
+
};
|
|
2500
|
+
emit();
|
|
2501
|
+
throw error;
|
|
2502
|
+
}
|
|
2503
|
+
};
|
|
2504
|
+
const close = () => {
|
|
2505
|
+
closed = true;
|
|
2506
|
+
if (timer) {
|
|
2507
|
+
clearInterval(timer);
|
|
2508
|
+
timer = undefined;
|
|
2509
|
+
}
|
|
2510
|
+
listeners.clear();
|
|
2511
|
+
};
|
|
2512
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
2513
|
+
timer = setInterval(() => {
|
|
2514
|
+
refresh().catch(() => {});
|
|
2515
|
+
}, options.intervalMs);
|
|
2516
|
+
}
|
|
2517
|
+
return {
|
|
2518
|
+
close,
|
|
2519
|
+
getServerSnapshot: () => snapshot,
|
|
2520
|
+
getSnapshot: () => snapshot,
|
|
2521
|
+
refresh,
|
|
2522
|
+
subscribe: (listener) => {
|
|
2523
|
+
listeners.add(listener);
|
|
2524
|
+
return () => {
|
|
2525
|
+
listeners.delete(listener);
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
};
|
|
2529
|
+
};
|
|
2530
|
+
|
|
2531
|
+
// src/angular/voice-provider-capabilities.service.ts
|
|
2532
|
+
var _dec = [
|
|
2533
|
+
Injectable9({ providedIn: "root" })
|
|
2534
|
+
];
|
|
2535
|
+
var _init = __decoratorStart(undefined);
|
|
2536
|
+
|
|
2537
|
+
class VoiceProviderCapabilitiesService {
|
|
2538
|
+
connect(path = "/api/provider-capabilities", options = {}) {
|
|
2539
|
+
const store = createVoiceProviderCapabilitiesStore(path, options);
|
|
2540
|
+
const errorSignal = signal9(null);
|
|
2541
|
+
const isLoadingSignal = signal9(false);
|
|
2542
|
+
const reportSignal = signal9(undefined);
|
|
2543
|
+
const updatedAtSignal = signal9(undefined);
|
|
2544
|
+
const sync = () => {
|
|
2545
|
+
const snapshot = store.getSnapshot();
|
|
2546
|
+
errorSignal.set(snapshot.error);
|
|
2547
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
2548
|
+
reportSignal.set(snapshot.report);
|
|
2549
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
2550
|
+
};
|
|
2551
|
+
const unsubscribe = store.subscribe(sync);
|
|
2552
|
+
sync();
|
|
2553
|
+
store.refresh().catch(() => {});
|
|
2554
|
+
return {
|
|
2555
|
+
close: () => {
|
|
2556
|
+
unsubscribe();
|
|
2557
|
+
store.close();
|
|
2558
|
+
},
|
|
2559
|
+
error: computed9(() => errorSignal()),
|
|
2560
|
+
isLoading: computed9(() => isLoadingSignal()),
|
|
2561
|
+
refresh: store.refresh,
|
|
2562
|
+
report: computed9(() => reportSignal()),
|
|
2563
|
+
updatedAt: computed9(() => updatedAtSignal())
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
VoiceProviderCapabilitiesService = __decorateElement(_init, 0, "VoiceProviderCapabilitiesService", _dec, VoiceProviderCapabilitiesService);
|
|
2568
|
+
__runInitializers(_init, 1, VoiceProviderCapabilitiesService);
|
|
2569
|
+
__decoratorMetadata(_init, VoiceProviderCapabilitiesService);
|
|
2570
|
+
let _VoiceProviderCapabilitiesService = VoiceProviderCapabilitiesService;
|
|
2571
|
+
// src/angular/voice-provider-contracts.service.ts
|
|
2572
|
+
import { computed as computed10, Injectable as Injectable10, signal as signal10 } from "@angular/core";
|
|
2573
|
+
|
|
2574
|
+
// src/client/providerContracts.ts
|
|
2575
|
+
var fetchVoiceProviderContracts = async (path = "/api/provider-contracts", options = {}) => {
|
|
2576
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2577
|
+
const response = await fetchImpl(path);
|
|
2578
|
+
if (!response.ok) {
|
|
2579
|
+
throw new Error(`Voice provider contracts failed: HTTP ${response.status}`);
|
|
2580
|
+
}
|
|
2581
|
+
return await response.json();
|
|
2582
|
+
};
|
|
2583
|
+
var createVoiceProviderContractsStore = (path = "/api/provider-contracts", options = {}) => {
|
|
2584
|
+
const listeners = new Set;
|
|
2585
|
+
let closed = false;
|
|
2586
|
+
let timer;
|
|
2587
|
+
let snapshot = {
|
|
2588
|
+
error: null,
|
|
2589
|
+
isLoading: false
|
|
2590
|
+
};
|
|
2591
|
+
const emit = () => {
|
|
2592
|
+
for (const listener of listeners) {
|
|
2593
|
+
listener();
|
|
2594
|
+
}
|
|
2595
|
+
};
|
|
2596
|
+
const refresh = async () => {
|
|
2597
|
+
if (closed) {
|
|
2598
|
+
return snapshot.report;
|
|
2599
|
+
}
|
|
2600
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
2601
|
+
emit();
|
|
2602
|
+
try {
|
|
2603
|
+
const report = await fetchVoiceProviderContracts(path, options);
|
|
2604
|
+
snapshot = {
|
|
2605
|
+
error: null,
|
|
2606
|
+
isLoading: false,
|
|
2607
|
+
report,
|
|
2608
|
+
updatedAt: Date.now()
|
|
2609
|
+
};
|
|
2610
|
+
emit();
|
|
2611
|
+
return report;
|
|
2612
|
+
} catch (error) {
|
|
2613
|
+
snapshot = {
|
|
2614
|
+
...snapshot,
|
|
2615
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2616
|
+
isLoading: false
|
|
2617
|
+
};
|
|
2618
|
+
emit();
|
|
2619
|
+
throw error;
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
const close = () => {
|
|
2623
|
+
closed = true;
|
|
2624
|
+
if (timer) {
|
|
2625
|
+
clearInterval(timer);
|
|
2626
|
+
timer = undefined;
|
|
2627
|
+
}
|
|
2628
|
+
listeners.clear();
|
|
2629
|
+
};
|
|
2630
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
2631
|
+
timer = setInterval(() => {
|
|
2632
|
+
refresh().catch(() => {});
|
|
2633
|
+
}, options.intervalMs);
|
|
2634
|
+
}
|
|
2635
|
+
return {
|
|
2636
|
+
close,
|
|
2637
|
+
getServerSnapshot: () => snapshot,
|
|
2638
|
+
getSnapshot: () => snapshot,
|
|
2639
|
+
refresh,
|
|
2640
|
+
subscribe: (listener) => {
|
|
2641
|
+
listeners.add(listener);
|
|
2642
|
+
return () => {
|
|
2643
|
+
listeners.delete(listener);
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
};
|
|
2648
|
+
|
|
2649
|
+
// src/angular/voice-provider-contracts.service.ts
|
|
2650
|
+
var _dec = [
|
|
2651
|
+
Injectable10({ providedIn: "root" })
|
|
2652
|
+
];
|
|
2653
|
+
var _init = __decoratorStart(undefined);
|
|
2654
|
+
|
|
2655
|
+
class VoiceProviderContractsService {
|
|
2656
|
+
connect(path = "/api/provider-contracts", options = {}) {
|
|
2657
|
+
const store = createVoiceProviderContractsStore(path, options);
|
|
2658
|
+
const errorSignal = signal10(null);
|
|
2659
|
+
const isLoadingSignal = signal10(false);
|
|
2660
|
+
const reportSignal = signal10(undefined);
|
|
2661
|
+
const updatedAtSignal = signal10(undefined);
|
|
2662
|
+
const sync = () => {
|
|
2663
|
+
const snapshot = store.getSnapshot();
|
|
2664
|
+
errorSignal.set(snapshot.error);
|
|
2665
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
2666
|
+
reportSignal.set(snapshot.report);
|
|
2667
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
2668
|
+
};
|
|
2669
|
+
const unsubscribe = store.subscribe(sync);
|
|
2670
|
+
sync();
|
|
2671
|
+
store.refresh().catch(() => {});
|
|
2672
|
+
return {
|
|
2673
|
+
close: () => {
|
|
2674
|
+
unsubscribe();
|
|
2675
|
+
store.close();
|
|
2676
|
+
},
|
|
2677
|
+
error: computed10(() => errorSignal()),
|
|
2678
|
+
isLoading: computed10(() => isLoadingSignal()),
|
|
2679
|
+
refresh: store.refresh,
|
|
2680
|
+
report: computed10(() => reportSignal()),
|
|
2681
|
+
updatedAt: computed10(() => updatedAtSignal())
|
|
2682
|
+
};
|
|
785
2683
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
2684
|
+
}
|
|
2685
|
+
VoiceProviderContractsService = __decorateElement(_init, 0, "VoiceProviderContractsService", _dec, VoiceProviderContractsService);
|
|
2686
|
+
__runInitializers(_init, 1, VoiceProviderContractsService);
|
|
2687
|
+
__decoratorMetadata(_init, VoiceProviderContractsService);
|
|
2688
|
+
let _VoiceProviderContractsService = VoiceProviderContractsService;
|
|
2689
|
+
// src/angular/voice-provider-status.service.ts
|
|
2690
|
+
import { computed as computed11, Injectable as Injectable11, signal as signal11 } from "@angular/core";
|
|
2691
|
+
|
|
2692
|
+
// src/client/providerStatus.ts
|
|
2693
|
+
var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
|
|
2694
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2695
|
+
const response = await fetchImpl(path);
|
|
2696
|
+
if (!response.ok) {
|
|
2697
|
+
throw new Error(`Voice provider status failed: HTTP ${response.status}`);
|
|
789
2698
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
2699
|
+
return await response.json();
|
|
2700
|
+
};
|
|
2701
|
+
var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
|
|
2702
|
+
const listeners = new Set;
|
|
2703
|
+
let closed = false;
|
|
2704
|
+
let timer;
|
|
2705
|
+
let snapshot = {
|
|
2706
|
+
error: null,
|
|
2707
|
+
isLoading: false,
|
|
2708
|
+
providers: []
|
|
2709
|
+
};
|
|
2710
|
+
const emit = () => {
|
|
2711
|
+
for (const listener of listeners) {
|
|
2712
|
+
listener();
|
|
2713
|
+
}
|
|
2714
|
+
};
|
|
2715
|
+
const refresh = async () => {
|
|
2716
|
+
if (closed) {
|
|
2717
|
+
return snapshot.providers;
|
|
2718
|
+
}
|
|
2719
|
+
snapshot = {
|
|
2720
|
+
...snapshot,
|
|
2721
|
+
error: null,
|
|
2722
|
+
isLoading: true
|
|
2723
|
+
};
|
|
2724
|
+
emit();
|
|
2725
|
+
try {
|
|
2726
|
+
const providers = await fetchVoiceProviderStatus(path, options);
|
|
2727
|
+
snapshot = {
|
|
2728
|
+
error: null,
|
|
2729
|
+
isLoading: false,
|
|
2730
|
+
providers,
|
|
2731
|
+
updatedAt: Date.now()
|
|
2732
|
+
};
|
|
2733
|
+
emit();
|
|
2734
|
+
return providers;
|
|
2735
|
+
} catch (error) {
|
|
2736
|
+
snapshot = {
|
|
2737
|
+
...snapshot,
|
|
2738
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2739
|
+
isLoading: false
|
|
2740
|
+
};
|
|
2741
|
+
emit();
|
|
2742
|
+
throw error;
|
|
2743
|
+
}
|
|
2744
|
+
};
|
|
2745
|
+
const close = () => {
|
|
2746
|
+
closed = true;
|
|
2747
|
+
if (timer) {
|
|
2748
|
+
clearInterval(timer);
|
|
2749
|
+
timer = undefined;
|
|
2750
|
+
}
|
|
2751
|
+
listeners.clear();
|
|
2752
|
+
};
|
|
2753
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
2754
|
+
timer = setInterval(() => {
|
|
2755
|
+
refresh().catch(() => {});
|
|
2756
|
+
}, options.intervalMs);
|
|
2757
|
+
}
|
|
2758
|
+
return {
|
|
2759
|
+
close,
|
|
2760
|
+
getServerSnapshot: () => snapshot,
|
|
2761
|
+
getSnapshot: () => snapshot,
|
|
2762
|
+
refresh,
|
|
2763
|
+
subscribe: (listener) => {
|
|
2764
|
+
listeners.add(listener);
|
|
2765
|
+
return () => {
|
|
2766
|
+
listeners.delete(listener);
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
};
|
|
2771
|
+
|
|
2772
|
+
// src/angular/voice-provider-status.service.ts
|
|
2773
|
+
var _dec = [
|
|
2774
|
+
Injectable11({ providedIn: "root" })
|
|
2775
|
+
];
|
|
2776
|
+
var _init = __decoratorStart(undefined);
|
|
2777
|
+
|
|
2778
|
+
class VoiceProviderStatusService {
|
|
2779
|
+
connect(path = "/api/provider-status", options = {}) {
|
|
2780
|
+
const store = createVoiceProviderStatusStore(path, options);
|
|
2781
|
+
const errorSignal = signal11(null);
|
|
2782
|
+
const isLoadingSignal = signal11(false);
|
|
2783
|
+
const providersSignal = signal11([]);
|
|
2784
|
+
const updatedAtSignal = signal11(undefined);
|
|
2785
|
+
const sync = () => {
|
|
2786
|
+
const snapshot = store.getSnapshot();
|
|
2787
|
+
errorSignal.set(snapshot.error);
|
|
2788
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
2789
|
+
providersSignal.set([...snapshot.providers]);
|
|
2790
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
2791
|
+
};
|
|
2792
|
+
const unsubscribe = store.subscribe(sync);
|
|
2793
|
+
sync();
|
|
2794
|
+
store.refresh().catch(() => {});
|
|
2795
|
+
return {
|
|
2796
|
+
close: () => {
|
|
2797
|
+
unsubscribe();
|
|
2798
|
+
store.close();
|
|
2799
|
+
},
|
|
2800
|
+
error: computed11(() => errorSignal()),
|
|
2801
|
+
isLoading: computed11(() => isLoadingSignal()),
|
|
2802
|
+
providers: computed11(() => providersSignal()),
|
|
2803
|
+
refresh: store.refresh,
|
|
2804
|
+
updatedAt: computed11(() => updatedAtSignal())
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
VoiceProviderStatusService = __decorateElement(_init, 0, "VoiceProviderStatusService", _dec, VoiceProviderStatusService);
|
|
2809
|
+
__runInitializers(_init, 1, VoiceProviderStatusService);
|
|
2810
|
+
__decoratorMetadata(_init, VoiceProviderStatusService);
|
|
2811
|
+
let _VoiceProviderStatusService = VoiceProviderStatusService;
|
|
2812
|
+
// src/angular/voice-routing-status.service.ts
|
|
2813
|
+
import { Injectable as Injectable12, signal as signal12 } from "@angular/core";
|
|
2814
|
+
|
|
2815
|
+
// src/client/routingStatus.ts
|
|
2816
|
+
var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
|
|
2817
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2818
|
+
const response = await fetchImpl(path);
|
|
2819
|
+
if (!response.ok) {
|
|
2820
|
+
throw new Error(`Voice routing status failed: HTTP ${response.status}`);
|
|
2821
|
+
}
|
|
2822
|
+
return await response.json();
|
|
2823
|
+
};
|
|
2824
|
+
var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
|
|
2825
|
+
const listeners = new Set;
|
|
2826
|
+
let closed = false;
|
|
2827
|
+
let timer;
|
|
2828
|
+
let snapshot = {
|
|
2829
|
+
decision: null,
|
|
2830
|
+
error: null,
|
|
2831
|
+
isLoading: false
|
|
2832
|
+
};
|
|
2833
|
+
const emit = () => {
|
|
2834
|
+
for (const listener of listeners) {
|
|
2835
|
+
listener();
|
|
2836
|
+
}
|
|
2837
|
+
};
|
|
2838
|
+
const refresh = async () => {
|
|
2839
|
+
if (closed) {
|
|
2840
|
+
return snapshot.decision;
|
|
2841
|
+
}
|
|
2842
|
+
snapshot = {
|
|
2843
|
+
...snapshot,
|
|
2844
|
+
error: null,
|
|
2845
|
+
isLoading: true
|
|
2846
|
+
};
|
|
2847
|
+
emit();
|
|
2848
|
+
try {
|
|
2849
|
+
const decision = await fetchVoiceRoutingStatus(path, options);
|
|
2850
|
+
snapshot = {
|
|
2851
|
+
decision,
|
|
2852
|
+
error: null,
|
|
2853
|
+
isLoading: false,
|
|
2854
|
+
updatedAt: Date.now()
|
|
2855
|
+
};
|
|
2856
|
+
emit();
|
|
2857
|
+
return decision;
|
|
2858
|
+
} catch (error) {
|
|
2859
|
+
snapshot = {
|
|
2860
|
+
...snapshot,
|
|
2861
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2862
|
+
isLoading: false
|
|
2863
|
+
};
|
|
2864
|
+
emit();
|
|
2865
|
+
throw error;
|
|
2866
|
+
}
|
|
2867
|
+
};
|
|
2868
|
+
const close = () => {
|
|
2869
|
+
closed = true;
|
|
2870
|
+
if (timer) {
|
|
2871
|
+
clearInterval(timer);
|
|
2872
|
+
timer = undefined;
|
|
2873
|
+
}
|
|
2874
|
+
listeners.clear();
|
|
2875
|
+
};
|
|
2876
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
2877
|
+
timer = setInterval(() => {
|
|
2878
|
+
refresh().catch(() => {});
|
|
2879
|
+
}, options.intervalMs);
|
|
799
2880
|
}
|
|
800
|
-
return
|
|
2881
|
+
return {
|
|
2882
|
+
close,
|
|
2883
|
+
getServerSnapshot: () => snapshot,
|
|
2884
|
+
getSnapshot: () => snapshot,
|
|
2885
|
+
refresh,
|
|
2886
|
+
subscribe: (listener) => {
|
|
2887
|
+
listeners.add(listener);
|
|
2888
|
+
return () => {
|
|
2889
|
+
listeners.delete(listener);
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
};
|
|
801
2893
|
};
|
|
802
2894
|
|
|
803
|
-
// src/
|
|
804
|
-
var
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
2895
|
+
// src/angular/voice-routing-status.service.ts
|
|
2896
|
+
var _dec = [
|
|
2897
|
+
Injectable12({ providedIn: "root" })
|
|
2898
|
+
];
|
|
2899
|
+
var _init = __decoratorStart(undefined);
|
|
2900
|
+
|
|
2901
|
+
class VoiceRoutingStatusService {
|
|
2902
|
+
connect(path = "/api/routing/latest", options = {}) {
|
|
2903
|
+
const store = createVoiceRoutingStatusStore(path, options);
|
|
2904
|
+
const decisionSignal = signal12(null);
|
|
2905
|
+
const errorSignal = signal12(null);
|
|
2906
|
+
const isLoadingSignal = signal12(false);
|
|
2907
|
+
const updatedAtSignal = signal12(undefined);
|
|
2908
|
+
const sync = () => {
|
|
2909
|
+
const snapshot = store.getSnapshot();
|
|
2910
|
+
decisionSignal.set(snapshot.decision);
|
|
2911
|
+
errorSignal.set(snapshot.error);
|
|
2912
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
2913
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
2914
|
+
};
|
|
2915
|
+
const unsubscribe = store.subscribe(sync);
|
|
2916
|
+
sync();
|
|
2917
|
+
store.refresh().catch(() => {});
|
|
2918
|
+
return {
|
|
2919
|
+
close: () => {
|
|
2920
|
+
unsubscribe();
|
|
2921
|
+
store.close();
|
|
2922
|
+
},
|
|
2923
|
+
decision: decisionSignal.asReadonly(),
|
|
2924
|
+
error: errorSignal.asReadonly(),
|
|
2925
|
+
isLoading: isLoadingSignal.asReadonly(),
|
|
2926
|
+
refresh: store.refresh,
|
|
2927
|
+
updatedAt: updatedAtSignal.asReadonly()
|
|
2928
|
+
};
|
|
822
2929
|
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
silenceMs: 500,
|
|
838
|
-
speechThreshold: 0.016,
|
|
839
|
-
transcriptStabilityMs: 420
|
|
2930
|
+
}
|
|
2931
|
+
VoiceRoutingStatusService = __decorateElement(_init, 0, "VoiceRoutingStatusService", _dec, VoiceRoutingStatusService);
|
|
2932
|
+
__runInitializers(_init, 1, VoiceRoutingStatusService);
|
|
2933
|
+
__decoratorMetadata(_init, VoiceRoutingStatusService);
|
|
2934
|
+
let _VoiceRoutingStatusService = VoiceRoutingStatusService;
|
|
2935
|
+
// src/angular/voice-trace-timeline.service.ts
|
|
2936
|
+
import { computed as computed12, Injectable as Injectable13, signal as signal13 } from "@angular/core";
|
|
2937
|
+
|
|
2938
|
+
// src/client/traceTimeline.ts
|
|
2939
|
+
var fetchVoiceTraceTimeline = async (path = "/api/voice-traces", options = {}) => {
|
|
2940
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2941
|
+
const response = await fetchImpl(path);
|
|
2942
|
+
if (!response.ok) {
|
|
2943
|
+
throw new Error(`Voice trace timeline failed: HTTP ${response.status}`);
|
|
840
2944
|
}
|
|
2945
|
+
return await response.json();
|
|
841
2946
|
};
|
|
842
|
-
var
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
profile,
|
|
851
|
-
qualityProfile,
|
|
852
|
-
silenceMs: config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs,
|
|
853
|
-
speechThreshold: config?.speechThreshold ?? quality.speechThreshold ?? preset.speechThreshold,
|
|
854
|
-
transcriptStabilityMs: config?.transcriptStabilityMs ?? quality.transcriptStabilityMs ?? preset.transcriptStabilityMs
|
|
2947
|
+
var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) => {
|
|
2948
|
+
const listeners = new Set;
|
|
2949
|
+
let closed = false;
|
|
2950
|
+
let timer;
|
|
2951
|
+
let snapshot = {
|
|
2952
|
+
error: null,
|
|
2953
|
+
isLoading: false,
|
|
2954
|
+
report: null
|
|
855
2955
|
};
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
var PRESET_INPUTS = {
|
|
860
|
-
chat: {
|
|
861
|
-
audioConditioning: {
|
|
862
|
-
enabled: true,
|
|
863
|
-
maxGain: 2.5,
|
|
864
|
-
noiseGateAttenuation: 0,
|
|
865
|
-
noiseGateThreshold: 0.004,
|
|
866
|
-
targetLevel: 0.08
|
|
867
|
-
},
|
|
868
|
-
capture: {
|
|
869
|
-
channelCount: 1,
|
|
870
|
-
sampleRateHz: 16000
|
|
871
|
-
},
|
|
872
|
-
connection: {
|
|
873
|
-
maxReconnectAttempts: 10,
|
|
874
|
-
pingInterval: 30000,
|
|
875
|
-
reconnect: true
|
|
876
|
-
},
|
|
877
|
-
sttLifecycle: "continuous",
|
|
878
|
-
turnDetection: {
|
|
879
|
-
qualityProfile: "short-command",
|
|
880
|
-
profile: "balanced"
|
|
881
|
-
}
|
|
882
|
-
},
|
|
883
|
-
default: {
|
|
884
|
-
capture: {
|
|
885
|
-
channelCount: 1,
|
|
886
|
-
sampleRateHz: 16000
|
|
887
|
-
},
|
|
888
|
-
connection: {
|
|
889
|
-
maxReconnectAttempts: 10,
|
|
890
|
-
pingInterval: 30000,
|
|
891
|
-
reconnect: true
|
|
892
|
-
},
|
|
893
|
-
sttLifecycle: "continuous",
|
|
894
|
-
turnDetection: {
|
|
895
|
-
qualityProfile: "general",
|
|
896
|
-
profile: "fast"
|
|
897
|
-
}
|
|
898
|
-
},
|
|
899
|
-
dictation: {
|
|
900
|
-
audioConditioning: {
|
|
901
|
-
enabled: true,
|
|
902
|
-
maxGain: 2.25,
|
|
903
|
-
noiseGateAttenuation: 0.05,
|
|
904
|
-
noiseGateThreshold: 0.003,
|
|
905
|
-
targetLevel: 0.08
|
|
906
|
-
},
|
|
907
|
-
capture: {
|
|
908
|
-
channelCount: 1,
|
|
909
|
-
sampleRateHz: 16000
|
|
910
|
-
},
|
|
911
|
-
connection: {
|
|
912
|
-
maxReconnectAttempts: 12,
|
|
913
|
-
pingInterval: 30000,
|
|
914
|
-
reconnect: true
|
|
915
|
-
},
|
|
916
|
-
sttLifecycle: "continuous",
|
|
917
|
-
turnDetection: {
|
|
918
|
-
qualityProfile: "accent-heavy",
|
|
919
|
-
profile: "long-form"
|
|
920
|
-
}
|
|
921
|
-
},
|
|
922
|
-
"guided-intake": {
|
|
923
|
-
audioConditioning: {
|
|
924
|
-
enabled: true,
|
|
925
|
-
maxGain: 2.5,
|
|
926
|
-
noiseGateAttenuation: 0,
|
|
927
|
-
noiseGateThreshold: 0.004,
|
|
928
|
-
targetLevel: 0.08
|
|
929
|
-
},
|
|
930
|
-
capture: {
|
|
931
|
-
channelCount: 1,
|
|
932
|
-
sampleRateHz: 16000
|
|
933
|
-
},
|
|
934
|
-
connection: {
|
|
935
|
-
maxReconnectAttempts: 12,
|
|
936
|
-
pingInterval: 30000,
|
|
937
|
-
reconnect: true
|
|
938
|
-
},
|
|
939
|
-
sttLifecycle: "turn-scoped",
|
|
940
|
-
turnDetection: {
|
|
941
|
-
qualityProfile: "accent-heavy",
|
|
942
|
-
profile: "long-form"
|
|
2956
|
+
const emit = () => {
|
|
2957
|
+
for (const listener of listeners) {
|
|
2958
|
+
listener();
|
|
943
2959
|
}
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
maxGain: 3,
|
|
949
|
-
noiseGateAttenuation: 0.12,
|
|
950
|
-
noiseGateThreshold: 0.006,
|
|
951
|
-
targetLevel: 0.085
|
|
952
|
-
},
|
|
953
|
-
capture: {
|
|
954
|
-
channelCount: 1,
|
|
955
|
-
sampleRateHz: 16000
|
|
956
|
-
},
|
|
957
|
-
connection: {
|
|
958
|
-
maxReconnectAttempts: 14,
|
|
959
|
-
pingInterval: 45000,
|
|
960
|
-
reconnect: true
|
|
961
|
-
},
|
|
962
|
-
sttLifecycle: "continuous",
|
|
963
|
-
turnDetection: {
|
|
964
|
-
qualityProfile: "noisy-room",
|
|
965
|
-
profile: "long-form",
|
|
966
|
-
silenceMs: 2100,
|
|
967
|
-
speechThreshold: 0.02,
|
|
968
|
-
transcriptStabilityMs: 1650
|
|
2960
|
+
};
|
|
2961
|
+
const refresh = async () => {
|
|
2962
|
+
if (closed) {
|
|
2963
|
+
return snapshot.report;
|
|
969
2964
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
transcriptStabilityMs: 300
|
|
2965
|
+
snapshot = {
|
|
2966
|
+
...snapshot,
|
|
2967
|
+
error: null,
|
|
2968
|
+
isLoading: true
|
|
2969
|
+
};
|
|
2970
|
+
emit();
|
|
2971
|
+
try {
|
|
2972
|
+
const report = await fetchVoiceTraceTimeline(path, options);
|
|
2973
|
+
snapshot = {
|
|
2974
|
+
error: null,
|
|
2975
|
+
isLoading: false,
|
|
2976
|
+
report,
|
|
2977
|
+
updatedAt: Date.now()
|
|
2978
|
+
};
|
|
2979
|
+
emit();
|
|
2980
|
+
return report;
|
|
2981
|
+
} catch (error) {
|
|
2982
|
+
snapshot = {
|
|
2983
|
+
...snapshot,
|
|
2984
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2985
|
+
isLoading: false
|
|
2986
|
+
};
|
|
2987
|
+
emit();
|
|
2988
|
+
throw error;
|
|
995
2989
|
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
noiseGateThreshold: 0.005,
|
|
1003
|
-
targetLevel: 0.08
|
|
1004
|
-
},
|
|
1005
|
-
capture: {
|
|
1006
|
-
channelCount: 1,
|
|
1007
|
-
sampleRateHz: 16000
|
|
1008
|
-
},
|
|
1009
|
-
connection: {
|
|
1010
|
-
maxReconnectAttempts: 14,
|
|
1011
|
-
pingInterval: 45000,
|
|
1012
|
-
reconnect: true
|
|
1013
|
-
},
|
|
1014
|
-
sttLifecycle: "continuous",
|
|
1015
|
-
turnDetection: {
|
|
1016
|
-
qualityProfile: "noisy-room",
|
|
1017
|
-
profile: "long-form",
|
|
1018
|
-
silenceMs: 620,
|
|
1019
|
-
speechThreshold: 0.012,
|
|
1020
|
-
transcriptStabilityMs: 280
|
|
2990
|
+
};
|
|
2991
|
+
const close = () => {
|
|
2992
|
+
closed = true;
|
|
2993
|
+
if (timer) {
|
|
2994
|
+
clearInterval(timer);
|
|
2995
|
+
timer = undefined;
|
|
1021
2996
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
},
|
|
1040
|
-
sttLifecycle: "continuous",
|
|
1041
|
-
turnDetection: {
|
|
1042
|
-
qualityProfile: "noisy-room",
|
|
1043
|
-
profile: "long-form"
|
|
2997
|
+
listeners.clear();
|
|
2998
|
+
};
|
|
2999
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
3000
|
+
timer = setInterval(() => {
|
|
3001
|
+
refresh().catch(() => {});
|
|
3002
|
+
}, options.intervalMs);
|
|
3003
|
+
}
|
|
3004
|
+
return {
|
|
3005
|
+
close,
|
|
3006
|
+
getServerSnapshot: () => snapshot,
|
|
3007
|
+
getSnapshot: () => snapshot,
|
|
3008
|
+
refresh,
|
|
3009
|
+
subscribe: (listener) => {
|
|
3010
|
+
listeners.add(listener);
|
|
3011
|
+
return () => {
|
|
3012
|
+
listeners.delete(listener);
|
|
3013
|
+
};
|
|
1044
3014
|
}
|
|
3015
|
+
};
|
|
3016
|
+
};
|
|
3017
|
+
|
|
3018
|
+
// src/angular/voice-trace-timeline.service.ts
|
|
3019
|
+
var _dec = [
|
|
3020
|
+
Injectable13({ providedIn: "root" })
|
|
3021
|
+
];
|
|
3022
|
+
var _init = __decoratorStart(undefined);
|
|
3023
|
+
|
|
3024
|
+
class VoiceTraceTimelineService {
|
|
3025
|
+
connect(path = "/api/voice-traces", options = {}) {
|
|
3026
|
+
const store = createVoiceTraceTimelineStore(path, options);
|
|
3027
|
+
const errorSignal = signal13(null);
|
|
3028
|
+
const isLoadingSignal = signal13(false);
|
|
3029
|
+
const reportSignal = signal13(null);
|
|
3030
|
+
const updatedAtSignal = signal13(undefined);
|
|
3031
|
+
const sync = () => {
|
|
3032
|
+
const snapshot = store.getSnapshot();
|
|
3033
|
+
errorSignal.set(snapshot.error);
|
|
3034
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
3035
|
+
reportSignal.set(snapshot.report);
|
|
3036
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
3037
|
+
};
|
|
3038
|
+
const unsubscribe = store.subscribe(sync);
|
|
3039
|
+
sync();
|
|
3040
|
+
store.refresh().catch(() => {});
|
|
3041
|
+
return {
|
|
3042
|
+
close: () => {
|
|
3043
|
+
unsubscribe();
|
|
3044
|
+
store.close();
|
|
3045
|
+
},
|
|
3046
|
+
error: computed12(() => errorSignal()),
|
|
3047
|
+
isLoading: computed12(() => isLoadingSignal()),
|
|
3048
|
+
refresh: store.refresh,
|
|
3049
|
+
report: computed12(() => reportSignal()),
|
|
3050
|
+
updatedAt: computed12(() => updatedAtSignal())
|
|
3051
|
+
};
|
|
1045
3052
|
}
|
|
3053
|
+
}
|
|
3054
|
+
VoiceTraceTimelineService = __decorateElement(_init, 0, "VoiceTraceTimelineService", _dec, VoiceTraceTimelineService);
|
|
3055
|
+
__runInitializers(_init, 1, VoiceTraceTimelineService);
|
|
3056
|
+
__decoratorMetadata(_init, VoiceTraceTimelineService);
|
|
3057
|
+
let _VoiceTraceTimelineService = VoiceTraceTimelineService;
|
|
3058
|
+
// src/angular/voice-agent-squad-status.service.ts
|
|
3059
|
+
import { computed as computed13, Injectable as Injectable14, signal as signal14 } from "@angular/core";
|
|
3060
|
+
|
|
3061
|
+
// src/client/agentSquadStatus.ts
|
|
3062
|
+
var getString = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
3063
|
+
var getPayloadString = (event, key) => getString(event.payload?.[key]);
|
|
3064
|
+
var eventStatus = (event) => {
|
|
3065
|
+
const status = getPayloadString(event, "status");
|
|
3066
|
+
if (status === "blocked")
|
|
3067
|
+
return "blocked";
|
|
3068
|
+
if (status === "unknown-target")
|
|
3069
|
+
return "unknown-target";
|
|
3070
|
+
if (status === "allowed")
|
|
3071
|
+
return "handoff";
|
|
3072
|
+
return event.type === "agent.result" ? "active" : "handoff";
|
|
1046
3073
|
};
|
|
1047
|
-
var
|
|
1048
|
-
const
|
|
3074
|
+
var deriveSessionSpecialist = (session) => {
|
|
3075
|
+
const events = [...session.events].sort((left, right) => left.at - right.at);
|
|
3076
|
+
const agentEvents = events.filter((event) => event.type === "agent.handoff" || event.type === "agent.context" || event.type === "agent.result" || event.type === "agent.model");
|
|
3077
|
+
const latest = agentEvents.at(-1);
|
|
3078
|
+
if (!latest) {
|
|
3079
|
+
return {
|
|
3080
|
+
lastEventAt: session.lastEventAt,
|
|
3081
|
+
sessionId: session.sessionId,
|
|
3082
|
+
status: "idle"
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
3085
|
+
const handoffEvents = events.filter((event) => event.type === "agent.handoff");
|
|
3086
|
+
const lastHandoff = handoffEvents.at(-1);
|
|
3087
|
+
const latestAgentId = getPayloadString(latest, "agentId");
|
|
3088
|
+
const handoffStatus = lastHandoff ? eventStatus(lastHandoff) : undefined;
|
|
3089
|
+
const currentTarget = handoffStatus === "blocked" || handoffStatus === "unknown-target" ? getPayloadString(lastHandoff, "fromAgentId") ?? latestAgentId : getPayloadString(lastHandoff ?? latest, "targetAgentId") ?? latestAgentId;
|
|
1049
3090
|
return {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
name,
|
|
1059
|
-
sttLifecycle: preset.sttLifecycle ?? "continuous",
|
|
1060
|
-
turnDetection: resolveTurnDetectionConfig(preset.turnDetection)
|
|
3091
|
+
fromAgentId: getPayloadString(lastHandoff ?? latest, "fromAgentId"),
|
|
3092
|
+
lastEventAt: latest.at,
|
|
3093
|
+
reason: getPayloadString(lastHandoff ?? latest, "reason") ?? getPayloadString(latest, "handoffTarget"),
|
|
3094
|
+
sessionId: session.sessionId,
|
|
3095
|
+
status: lastHandoff ? eventStatus(lastHandoff) : "active",
|
|
3096
|
+
summary: getPayloadString(lastHandoff ?? latest, "summary"),
|
|
3097
|
+
targetAgentId: currentTarget,
|
|
3098
|
+
turnId: latest.turnId
|
|
1061
3099
|
};
|
|
1062
3100
|
};
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
var createVoiceController = (path, options = {}) => {
|
|
1079
|
-
const preset = resolveVoiceRuntimePreset(options.preset);
|
|
1080
|
-
const stream = createVoiceStream(path, {
|
|
1081
|
-
...preset.connection,
|
|
1082
|
-
...options.connection
|
|
3101
|
+
var buildVoiceAgentSquadStatusReport = (timeline, options = {}) => {
|
|
3102
|
+
const sessions = (timeline?.sessions ?? []).filter((session) => !options.sessionId || session.sessionId === options.sessionId).map(deriveSessionSpecialist).sort((left, right) => (right.lastEventAt ?? 0) - (left.lastEventAt ?? 0));
|
|
3103
|
+
const active = sessions.filter((session) => session.status !== "idle");
|
|
3104
|
+
return {
|
|
3105
|
+
active,
|
|
3106
|
+
checkedAt: timeline?.checkedAt,
|
|
3107
|
+
current: active[0] ?? sessions[0],
|
|
3108
|
+
sessionCount: sessions.length,
|
|
3109
|
+
sessions
|
|
3110
|
+
};
|
|
3111
|
+
};
|
|
3112
|
+
var createVoiceAgentSquadStatusStore = (path = "/api/voice-traces", options = {}) => {
|
|
3113
|
+
const timelineStore = createVoiceTraceTimelineStore(path, options);
|
|
3114
|
+
const getReport = () => buildVoiceAgentSquadStatusReport(timelineStore.getSnapshot().report, {
|
|
3115
|
+
sessionId: options.sessionId
|
|
1083
3116
|
});
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
3117
|
+
const getSnapshot = () => {
|
|
3118
|
+
const snapshot = timelineStore.getSnapshot();
|
|
3119
|
+
return {
|
|
3120
|
+
error: snapshot.error,
|
|
3121
|
+
isLoading: snapshot.isLoading,
|
|
3122
|
+
report: getReport(),
|
|
3123
|
+
updatedAt: snapshot.updatedAt
|
|
3124
|
+
};
|
|
1091
3125
|
};
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
3126
|
+
return {
|
|
3127
|
+
close: timelineStore.close,
|
|
3128
|
+
getServerSnapshot: getSnapshot,
|
|
3129
|
+
getSnapshot,
|
|
3130
|
+
refresh: timelineStore.refresh,
|
|
3131
|
+
subscribe: timelineStore.subscribe
|
|
3132
|
+
};
|
|
3133
|
+
};
|
|
3134
|
+
|
|
3135
|
+
// src/angular/voice-agent-squad-status.service.ts
|
|
3136
|
+
var _dec = [
|
|
3137
|
+
Injectable14({ providedIn: "root" })
|
|
3138
|
+
];
|
|
3139
|
+
var _init = __decoratorStart(undefined);
|
|
3140
|
+
|
|
3141
|
+
class VoiceAgentSquadStatusService {
|
|
3142
|
+
connect(path = "/api/voice-traces", options = {}) {
|
|
3143
|
+
const store = createVoiceAgentSquadStatusStore(path, options);
|
|
3144
|
+
const errorSignal = signal14(null);
|
|
3145
|
+
const isLoadingSignal = signal14(false);
|
|
3146
|
+
const reportSignal = signal14(undefined);
|
|
3147
|
+
const updatedAtSignal = signal14(undefined);
|
|
3148
|
+
const sync = () => {
|
|
3149
|
+
const snapshot = store.getSnapshot();
|
|
3150
|
+
errorSignal.set(snapshot.error);
|
|
3151
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
3152
|
+
reportSignal.set(snapshot.report);
|
|
3153
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
1104
3154
|
};
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
3155
|
+
const unsubscribe = store.subscribe(sync);
|
|
3156
|
+
sync();
|
|
3157
|
+
store.refresh().catch(() => {});
|
|
3158
|
+
return {
|
|
3159
|
+
close: () => {
|
|
3160
|
+
unsubscribe();
|
|
3161
|
+
store.close();
|
|
3162
|
+
},
|
|
3163
|
+
current: computed13(() => reportSignal()?.current),
|
|
3164
|
+
error: computed13(() => errorSignal()),
|
|
3165
|
+
isLoading: computed13(() => isLoadingSignal()),
|
|
3166
|
+
refresh: store.refresh,
|
|
3167
|
+
report: computed13(() => reportSignal()),
|
|
3168
|
+
updatedAt: computed13(() => updatedAtSignal())
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
VoiceAgentSquadStatusService = __decorateElement(_init, 0, "VoiceAgentSquadStatusService", _dec, VoiceAgentSquadStatusService);
|
|
3173
|
+
__runInitializers(_init, 1, VoiceAgentSquadStatusService);
|
|
3174
|
+
__decoratorMetadata(_init, VoiceAgentSquadStatusService);
|
|
3175
|
+
let _VoiceAgentSquadStatusService = VoiceAgentSquadStatusService;
|
|
3176
|
+
// src/angular/voice-turn-latency.service.ts
|
|
3177
|
+
import { computed as computed14, Injectable as Injectable15, signal as signal15 } from "@angular/core";
|
|
3178
|
+
|
|
3179
|
+
// src/client/turnLatency.ts
|
|
3180
|
+
var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) => {
|
|
3181
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
3182
|
+
const response = await fetchImpl(path);
|
|
3183
|
+
if (!response.ok) {
|
|
3184
|
+
throw new Error(`Voice turn latency failed: HTTP ${response.status}`);
|
|
3185
|
+
}
|
|
3186
|
+
return await response.json();
|
|
3187
|
+
};
|
|
3188
|
+
var runVoiceTurnLatencyProof = async (path, options = {}) => {
|
|
3189
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
3190
|
+
const response = await fetchImpl(path, { method: "POST" });
|
|
3191
|
+
if (!response.ok) {
|
|
3192
|
+
throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
|
|
3193
|
+
}
|
|
3194
|
+
return response.json();
|
|
3195
|
+
};
|
|
3196
|
+
var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
|
|
3197
|
+
const listeners = new Set;
|
|
3198
|
+
let closed = false;
|
|
3199
|
+
let timer;
|
|
3200
|
+
let snapshot = {
|
|
3201
|
+
error: null,
|
|
3202
|
+
isLoading: false
|
|
3203
|
+
};
|
|
3204
|
+
const emit = () => {
|
|
3205
|
+
for (const listener of listeners) {
|
|
3206
|
+
listener();
|
|
3207
|
+
}
|
|
3208
|
+
};
|
|
3209
|
+
const refresh = async () => {
|
|
3210
|
+
if (closed) {
|
|
3211
|
+
return snapshot.report;
|
|
3212
|
+
}
|
|
3213
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
3214
|
+
emit();
|
|
3215
|
+
try {
|
|
3216
|
+
const report = await fetchVoiceTurnLatency(path, options);
|
|
3217
|
+
snapshot = {
|
|
3218
|
+
error: null,
|
|
3219
|
+
isLoading: false,
|
|
3220
|
+
report,
|
|
3221
|
+
updatedAt: Date.now()
|
|
3222
|
+
};
|
|
3223
|
+
emit();
|
|
3224
|
+
return report;
|
|
3225
|
+
} catch (error) {
|
|
3226
|
+
snapshot = {
|
|
3227
|
+
...snapshot,
|
|
3228
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3229
|
+
isLoading: false
|
|
3230
|
+
};
|
|
3231
|
+
emit();
|
|
3232
|
+
throw error;
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
const runProof = async () => {
|
|
3236
|
+
if (!options.proofPath) {
|
|
3237
|
+
throw new Error("Voice turn latency proof path is not configured.");
|
|
3238
|
+
}
|
|
3239
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
3240
|
+
emit();
|
|
3241
|
+
try {
|
|
3242
|
+
await runVoiceTurnLatencyProof(options.proofPath, options);
|
|
3243
|
+
return await refresh();
|
|
3244
|
+
} catch (error) {
|
|
3245
|
+
snapshot = {
|
|
3246
|
+
...snapshot,
|
|
3247
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3248
|
+
isLoading: false
|
|
1111
3249
|
};
|
|
3250
|
+
emit();
|
|
3251
|
+
throw error;
|
|
1112
3252
|
}
|
|
1113
|
-
notify();
|
|
1114
3253
|
};
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
3254
|
+
const close = () => {
|
|
3255
|
+
closed = true;
|
|
3256
|
+
if (timer) {
|
|
3257
|
+
clearInterval(timer);
|
|
3258
|
+
timer = undefined;
|
|
1120
3259
|
}
|
|
1121
|
-
|
|
1122
|
-
channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
|
|
1123
|
-
onLevel: options.capture?.onLevel,
|
|
1124
|
-
onAudio: (audio) => stream.sendAudio(audio),
|
|
1125
|
-
sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
|
|
1126
|
-
});
|
|
1127
|
-
return capture;
|
|
3260
|
+
listeners.clear();
|
|
1128
3261
|
};
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
3262
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
3263
|
+
timer = setInterval(() => {
|
|
3264
|
+
refresh().catch(() => {});
|
|
3265
|
+
}, options.intervalMs);
|
|
3266
|
+
}
|
|
3267
|
+
return {
|
|
3268
|
+
close,
|
|
3269
|
+
getServerSnapshot: () => snapshot,
|
|
3270
|
+
getSnapshot: () => snapshot,
|
|
3271
|
+
refresh,
|
|
3272
|
+
runProof,
|
|
3273
|
+
subscribe: (listener) => {
|
|
3274
|
+
listeners.add(listener);
|
|
3275
|
+
return () => {
|
|
3276
|
+
listeners.delete(listener);
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
};
|
|
3281
|
+
|
|
3282
|
+
// src/angular/voice-turn-latency.service.ts
|
|
3283
|
+
var _dec = [
|
|
3284
|
+
Injectable15({ providedIn: "root" })
|
|
3285
|
+
];
|
|
3286
|
+
var _init = __decoratorStart(undefined);
|
|
3287
|
+
|
|
3288
|
+
class VoiceTurnLatencyService {
|
|
3289
|
+
connect(path = "/api/turn-latency", options = {}) {
|
|
3290
|
+
const store = createVoiceTurnLatencyStore(path, options);
|
|
3291
|
+
const errorSignal = signal15(null);
|
|
3292
|
+
const isLoadingSignal = signal15(false);
|
|
3293
|
+
const reportSignal = signal15(undefined);
|
|
3294
|
+
const updatedAtSignal = signal15(undefined);
|
|
3295
|
+
const sync = () => {
|
|
3296
|
+
const snapshot = store.getSnapshot();
|
|
3297
|
+
errorSignal.set(snapshot.error);
|
|
3298
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
3299
|
+
reportSignal.set(snapshot.report);
|
|
3300
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
1135
3301
|
};
|
|
1136
|
-
|
|
3302
|
+
const unsubscribe = store.subscribe(sync);
|
|
3303
|
+
sync();
|
|
3304
|
+
store.refresh().catch(() => {});
|
|
3305
|
+
return {
|
|
3306
|
+
close: () => {
|
|
3307
|
+
unsubscribe();
|
|
3308
|
+
store.close();
|
|
3309
|
+
},
|
|
3310
|
+
error: computed14(() => errorSignal()),
|
|
3311
|
+
isLoading: computed14(() => isLoadingSignal()),
|
|
3312
|
+
refresh: store.refresh,
|
|
3313
|
+
report: computed14(() => reportSignal()),
|
|
3314
|
+
runProof: store.runProof,
|
|
3315
|
+
updatedAt: computed14(() => updatedAtSignal())
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
VoiceTurnLatencyService = __decorateElement(_init, 0, "VoiceTurnLatencyService", _dec, VoiceTurnLatencyService);
|
|
3320
|
+
__runInitializers(_init, 1, VoiceTurnLatencyService);
|
|
3321
|
+
__decoratorMetadata(_init, VoiceTurnLatencyService);
|
|
3322
|
+
let _VoiceTurnLatencyService = VoiceTurnLatencyService;
|
|
3323
|
+
// src/angular/voice-turn-quality.service.ts
|
|
3324
|
+
import { computed as computed15, Injectable as Injectable16, signal as signal16 } from "@angular/core";
|
|
3325
|
+
|
|
3326
|
+
// src/client/turnQuality.ts
|
|
3327
|
+
var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
|
|
3328
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
3329
|
+
const response = await fetchImpl(path);
|
|
3330
|
+
if (!response.ok) {
|
|
3331
|
+
throw new Error(`Voice turn quality failed: HTTP ${response.status}`);
|
|
3332
|
+
}
|
|
3333
|
+
return await response.json();
|
|
3334
|
+
};
|
|
3335
|
+
var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) => {
|
|
3336
|
+
const listeners = new Set;
|
|
3337
|
+
let closed = false;
|
|
3338
|
+
let timer;
|
|
3339
|
+
let snapshot = {
|
|
3340
|
+
error: null,
|
|
3341
|
+
isLoading: false
|
|
1137
3342
|
};
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
3343
|
+
const emit = () => {
|
|
3344
|
+
for (const listener of listeners) {
|
|
3345
|
+
listener();
|
|
3346
|
+
}
|
|
3347
|
+
};
|
|
3348
|
+
const refresh = async () => {
|
|
3349
|
+
if (closed) {
|
|
3350
|
+
return snapshot.report;
|
|
1141
3351
|
}
|
|
3352
|
+
snapshot = {
|
|
3353
|
+
...snapshot,
|
|
3354
|
+
error: null,
|
|
3355
|
+
isLoading: true
|
|
3356
|
+
};
|
|
3357
|
+
emit();
|
|
1142
3358
|
try {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
state = {
|
|
1150
|
-
...state,
|
|
1151
|
-
isRecording: true
|
|
3359
|
+
const report = await fetchVoiceTurnQuality(path, options);
|
|
3360
|
+
snapshot = {
|
|
3361
|
+
error: null,
|
|
3362
|
+
isLoading: false,
|
|
3363
|
+
report,
|
|
3364
|
+
updatedAt: Date.now()
|
|
1152
3365
|
};
|
|
1153
|
-
|
|
3366
|
+
emit();
|
|
3367
|
+
return report;
|
|
1154
3368
|
} catch (error) {
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
recordingError: error instanceof Error ? error.message : String(error)
|
|
3369
|
+
snapshot = {
|
|
3370
|
+
...snapshot,
|
|
3371
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3372
|
+
isLoading: false
|
|
1160
3373
|
};
|
|
1161
|
-
|
|
3374
|
+
emit();
|
|
1162
3375
|
throw error;
|
|
1163
3376
|
}
|
|
1164
3377
|
};
|
|
1165
3378
|
const close = () => {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
3379
|
+
closed = true;
|
|
3380
|
+
if (timer) {
|
|
3381
|
+
clearInterval(timer);
|
|
3382
|
+
timer = undefined;
|
|
3383
|
+
}
|
|
3384
|
+
listeners.clear();
|
|
1169
3385
|
};
|
|
3386
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
3387
|
+
timer = setInterval(() => {
|
|
3388
|
+
refresh().catch(() => {});
|
|
3389
|
+
}, options.intervalMs);
|
|
3390
|
+
}
|
|
1170
3391
|
return {
|
|
1171
|
-
bindHTMX(bindingOptions) {
|
|
1172
|
-
return bindVoiceHTMX(stream, bindingOptions);
|
|
1173
|
-
},
|
|
1174
3392
|
close,
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
getSnapshot: () => state,
|
|
1181
|
-
get isConnected() {
|
|
1182
|
-
return state.isConnected;
|
|
1183
|
-
},
|
|
1184
|
-
get isRecording() {
|
|
1185
|
-
return state.isRecording;
|
|
1186
|
-
},
|
|
1187
|
-
get partial() {
|
|
1188
|
-
return state.partial;
|
|
1189
|
-
},
|
|
1190
|
-
get recordingError() {
|
|
1191
|
-
return state.recordingError;
|
|
1192
|
-
},
|
|
1193
|
-
sendAudio: (audio) => stream.sendAudio(audio),
|
|
1194
|
-
get sessionId() {
|
|
1195
|
-
return state.sessionId;
|
|
1196
|
-
},
|
|
1197
|
-
get scenarioId() {
|
|
1198
|
-
return state.scenarioId;
|
|
1199
|
-
},
|
|
1200
|
-
startRecording,
|
|
1201
|
-
get status() {
|
|
1202
|
-
return state.status;
|
|
1203
|
-
},
|
|
1204
|
-
stopRecording,
|
|
1205
|
-
subscribe: (subscriber) => {
|
|
1206
|
-
subscribers.add(subscriber);
|
|
3393
|
+
getServerSnapshot: () => snapshot,
|
|
3394
|
+
getSnapshot: () => snapshot,
|
|
3395
|
+
refresh,
|
|
3396
|
+
subscribe: (listener) => {
|
|
3397
|
+
listeners.add(listener);
|
|
1207
3398
|
return () => {
|
|
1208
|
-
|
|
3399
|
+
listeners.delete(listener);
|
|
1209
3400
|
};
|
|
1210
|
-
},
|
|
1211
|
-
toggleRecording: async () => {
|
|
1212
|
-
if (state.isRecording) {
|
|
1213
|
-
stopRecording();
|
|
1214
|
-
return;
|
|
1215
|
-
}
|
|
1216
|
-
await startRecording();
|
|
1217
|
-
},
|
|
1218
|
-
get turns() {
|
|
1219
|
-
return state.turns;
|
|
1220
|
-
},
|
|
1221
|
-
get assistantTexts() {
|
|
1222
|
-
return state.assistantTexts;
|
|
1223
|
-
},
|
|
1224
|
-
get assistantAudio() {
|
|
1225
|
-
return state.assistantAudio;
|
|
1226
3401
|
}
|
|
1227
3402
|
};
|
|
1228
3403
|
};
|
|
1229
3404
|
|
|
1230
|
-
// src/angular/voice-
|
|
3405
|
+
// src/angular/voice-turn-quality.service.ts
|
|
1231
3406
|
var _dec = [
|
|
1232
|
-
|
|
3407
|
+
Injectable16({ providedIn: "root" })
|
|
1233
3408
|
];
|
|
1234
3409
|
var _init = __decoratorStart(undefined);
|
|
1235
3410
|
|
|
1236
|
-
class
|
|
1237
|
-
connect(path, options = {}) {
|
|
1238
|
-
const
|
|
1239
|
-
const
|
|
1240
|
-
const
|
|
1241
|
-
const
|
|
1242
|
-
const
|
|
1243
|
-
const isRecordingSignal = signal2(false);
|
|
1244
|
-
const partialSignal = signal2("");
|
|
1245
|
-
const recordingErrorSignal = signal2(null);
|
|
1246
|
-
const sessionIdSignal = signal2(controller.sessionId);
|
|
1247
|
-
const statusSignal = signal2(controller.status);
|
|
1248
|
-
const turnsSignal = signal2([]);
|
|
3411
|
+
class VoiceTurnQualityService {
|
|
3412
|
+
connect(path = "/api/turn-quality", options = {}) {
|
|
3413
|
+
const store = createVoiceTurnQualityStore(path, options);
|
|
3414
|
+
const errorSignal = signal16(null);
|
|
3415
|
+
const isLoadingSignal = signal16(false);
|
|
3416
|
+
const reportSignal = signal16(undefined);
|
|
3417
|
+
const updatedAtSignal = signal16(undefined);
|
|
1249
3418
|
const sync = () => {
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
partialSignal.set(controller.partial);
|
|
1256
|
-
recordingErrorSignal.set(controller.recordingError);
|
|
1257
|
-
sessionIdSignal.set(controller.sessionId);
|
|
1258
|
-
statusSignal.set(controller.status);
|
|
1259
|
-
turnsSignal.set([...controller.turns]);
|
|
3419
|
+
const snapshot = store.getSnapshot();
|
|
3420
|
+
errorSignal.set(snapshot.error);
|
|
3421
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
3422
|
+
reportSignal.set(snapshot.report);
|
|
3423
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
1260
3424
|
};
|
|
1261
|
-
const unsubscribe =
|
|
3425
|
+
const unsubscribe = store.subscribe(sync);
|
|
1262
3426
|
sync();
|
|
3427
|
+
store.refresh().catch(() => {});
|
|
1263
3428
|
return {
|
|
1264
|
-
assistantAudio: computed2(() => assistantAudioSignal()),
|
|
1265
|
-
assistantTexts: computed2(() => assistantTextsSignal()),
|
|
1266
|
-
bindHTMX: controller.bindHTMX,
|
|
1267
3429
|
close: () => {
|
|
1268
3430
|
unsubscribe();
|
|
1269
|
-
|
|
3431
|
+
store.close();
|
|
1270
3432
|
},
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
recordingError: computed2(() => recordingErrorSignal()),
|
|
1277
|
-
sendAudio: (audio) => controller.sendAudio(audio),
|
|
1278
|
-
sessionId: computed2(() => sessionIdSignal()),
|
|
1279
|
-
startRecording: () => controller.startRecording(),
|
|
1280
|
-
status: computed2(() => statusSignal()),
|
|
1281
|
-
stopRecording: () => controller.stopRecording(),
|
|
1282
|
-
toggleRecording: () => controller.toggleRecording(),
|
|
1283
|
-
turns: computed2(() => turnsSignal())
|
|
3433
|
+
error: computed15(() => errorSignal()),
|
|
3434
|
+
isLoading: computed15(() => isLoadingSignal()),
|
|
3435
|
+
refresh: store.refresh,
|
|
3436
|
+
report: computed15(() => reportSignal()),
|
|
3437
|
+
updatedAt: computed15(() => updatedAtSignal())
|
|
1284
3438
|
};
|
|
1285
3439
|
}
|
|
1286
3440
|
}
|
|
1287
|
-
|
|
1288
|
-
__runInitializers(_init, 1,
|
|
1289
|
-
__decoratorMetadata(_init,
|
|
1290
|
-
let
|
|
1291
|
-
// src/angular/voice-
|
|
1292
|
-
import { computed as
|
|
3441
|
+
VoiceTurnQualityService = __decorateElement(_init, 0, "VoiceTurnQualityService", _dec, VoiceTurnQualityService);
|
|
3442
|
+
__runInitializers(_init, 1, VoiceTurnQualityService);
|
|
3443
|
+
__decoratorMetadata(_init, VoiceTurnQualityService);
|
|
3444
|
+
let _VoiceTurnQualityService = VoiceTurnQualityService;
|
|
3445
|
+
// src/angular/voice-workflow-status.service.ts
|
|
3446
|
+
import { computed as computed16, Injectable as Injectable17, signal as signal17 } from "@angular/core";
|
|
1293
3447
|
|
|
1294
|
-
// src/client/
|
|
1295
|
-
var
|
|
3448
|
+
// src/client/workflowStatus.ts
|
|
3449
|
+
var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
|
|
1296
3450
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1297
3451
|
const response = await fetchImpl(path);
|
|
1298
3452
|
if (!response.ok) {
|
|
1299
|
-
throw new Error(`Voice
|
|
3453
|
+
throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
|
|
1300
3454
|
}
|
|
1301
3455
|
return await response.json();
|
|
1302
3456
|
};
|
|
1303
|
-
var
|
|
3457
|
+
var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
|
|
1304
3458
|
const listeners = new Set;
|
|
1305
3459
|
let closed = false;
|
|
1306
3460
|
let timer;
|
|
1307
3461
|
let snapshot = {
|
|
1308
3462
|
error: null,
|
|
1309
|
-
isLoading: false
|
|
1310
|
-
providers: []
|
|
3463
|
+
isLoading: false
|
|
1311
3464
|
};
|
|
1312
3465
|
const emit = () => {
|
|
1313
3466
|
for (const listener of listeners) {
|
|
@@ -1316,7 +3469,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
|
|
|
1316
3469
|
};
|
|
1317
3470
|
const refresh = async () => {
|
|
1318
3471
|
if (closed) {
|
|
1319
|
-
return snapshot.
|
|
3472
|
+
return snapshot.report;
|
|
1320
3473
|
}
|
|
1321
3474
|
snapshot = {
|
|
1322
3475
|
...snapshot,
|
|
@@ -1325,15 +3478,15 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
|
|
|
1325
3478
|
};
|
|
1326
3479
|
emit();
|
|
1327
3480
|
try {
|
|
1328
|
-
const
|
|
3481
|
+
const report = await fetchVoiceWorkflowStatus(path, options);
|
|
1329
3482
|
snapshot = {
|
|
1330
3483
|
error: null,
|
|
1331
3484
|
isLoading: false,
|
|
1332
|
-
|
|
3485
|
+
report,
|
|
1333
3486
|
updatedAt: Date.now()
|
|
1334
3487
|
};
|
|
1335
3488
|
emit();
|
|
1336
|
-
return
|
|
3489
|
+
return report;
|
|
1337
3490
|
} catch (error) {
|
|
1338
3491
|
snapshot = {
|
|
1339
3492
|
...snapshot,
|
|
@@ -1352,7 +3505,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
|
|
|
1352
3505
|
}
|
|
1353
3506
|
listeners.clear();
|
|
1354
3507
|
};
|
|
1355
|
-
if (options.intervalMs && options.intervalMs > 0) {
|
|
3508
|
+
if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
|
|
1356
3509
|
timer = setInterval(() => {
|
|
1357
3510
|
refresh().catch(() => {});
|
|
1358
3511
|
}, options.intervalMs);
|
|
@@ -1371,48 +3524,64 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
|
|
|
1371
3524
|
};
|
|
1372
3525
|
};
|
|
1373
3526
|
|
|
1374
|
-
// src/angular/voice-
|
|
3527
|
+
// src/angular/voice-workflow-status.service.ts
|
|
1375
3528
|
var _dec = [
|
|
1376
|
-
|
|
3529
|
+
Injectable17({ providedIn: "root" })
|
|
1377
3530
|
];
|
|
1378
3531
|
var _init = __decoratorStart(undefined);
|
|
1379
3532
|
|
|
1380
|
-
class
|
|
1381
|
-
connect(path = "/
|
|
1382
|
-
const store =
|
|
1383
|
-
const errorSignal =
|
|
1384
|
-
const isLoadingSignal =
|
|
1385
|
-
const
|
|
1386
|
-
const updatedAtSignal =
|
|
3533
|
+
class VoiceWorkflowStatusService {
|
|
3534
|
+
connect(path = "/evals/scenarios/json", options = {}) {
|
|
3535
|
+
const store = createVoiceWorkflowStatusStore(path, options);
|
|
3536
|
+
const errorSignal = signal17(null);
|
|
3537
|
+
const isLoadingSignal = signal17(false);
|
|
3538
|
+
const reportSignal = signal17(undefined);
|
|
3539
|
+
const updatedAtSignal = signal17(undefined);
|
|
1387
3540
|
const sync = () => {
|
|
1388
3541
|
const snapshot = store.getSnapshot();
|
|
1389
3542
|
errorSignal.set(snapshot.error);
|
|
1390
3543
|
isLoadingSignal.set(snapshot.isLoading);
|
|
1391
|
-
|
|
3544
|
+
reportSignal.set(snapshot.report);
|
|
1392
3545
|
updatedAtSignal.set(snapshot.updatedAt);
|
|
1393
3546
|
};
|
|
1394
3547
|
const unsubscribe = store.subscribe(sync);
|
|
1395
3548
|
sync();
|
|
1396
|
-
|
|
3549
|
+
if (typeof window !== "undefined") {
|
|
3550
|
+
store.refresh().catch(() => {});
|
|
3551
|
+
}
|
|
1397
3552
|
return {
|
|
1398
3553
|
close: () => {
|
|
1399
3554
|
unsubscribe();
|
|
1400
3555
|
store.close();
|
|
1401
3556
|
},
|
|
1402
|
-
error:
|
|
1403
|
-
isLoading:
|
|
1404
|
-
providers: computed3(() => providersSignal()),
|
|
3557
|
+
error: computed16(() => errorSignal()),
|
|
3558
|
+
isLoading: computed16(() => isLoadingSignal()),
|
|
1405
3559
|
refresh: store.refresh,
|
|
1406
|
-
|
|
3560
|
+
report: computed16(() => reportSignal()),
|
|
3561
|
+
updatedAt: computed16(() => updatedAtSignal())
|
|
1407
3562
|
};
|
|
1408
3563
|
}
|
|
1409
3564
|
}
|
|
1410
|
-
|
|
1411
|
-
__runInitializers(_init, 1,
|
|
1412
|
-
__decoratorMetadata(_init,
|
|
1413
|
-
let
|
|
3565
|
+
VoiceWorkflowStatusService = __decorateElement(_init, 0, "VoiceWorkflowStatusService", _dec, VoiceWorkflowStatusService);
|
|
3566
|
+
__runInitializers(_init, 1, VoiceWorkflowStatusService);
|
|
3567
|
+
__decoratorMetadata(_init, VoiceWorkflowStatusService);
|
|
3568
|
+
let _VoiceWorkflowStatusService = VoiceWorkflowStatusService;
|
|
1414
3569
|
export {
|
|
3570
|
+
VoiceWorkflowStatusService,
|
|
3571
|
+
VoiceTurnQualityService,
|
|
3572
|
+
VoiceTurnLatencyService,
|
|
3573
|
+
VoiceTraceTimelineService,
|
|
1415
3574
|
VoiceStreamService,
|
|
3575
|
+
VoiceRoutingStatusService,
|
|
1416
3576
|
VoiceProviderStatusService,
|
|
1417
|
-
|
|
3577
|
+
VoiceProviderContractsService,
|
|
3578
|
+
VoiceProviderCapabilitiesService,
|
|
3579
|
+
VoicePlatformCoverageService,
|
|
3580
|
+
VoiceOpsStatusService,
|
|
3581
|
+
VoiceOpsActionCenterService,
|
|
3582
|
+
VoiceLiveOpsService,
|
|
3583
|
+
VoiceDeliveryRuntimeService,
|
|
3584
|
+
VoiceControllerService,
|
|
3585
|
+
VoiceCampaignDialerProofService,
|
|
3586
|
+
VoiceAgentSquadStatusService
|
|
1418
3587
|
};
|