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