@absolutejs/voice 0.0.22-beta.16 → 0.0.22-beta.160
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 +1053 -5
- package/dist/agent.d.ts +24 -0
- package/dist/agentSquadContract.d.ts +64 -0
- package/dist/angular/index.d.ts +11 -0
- package/dist/angular/index.js +3072 -1106
- 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-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 +81 -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 +50 -0
- package/dist/client/index.js +2757 -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/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 +17792 -4316
- 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 +335 -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/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/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 +21 -0
- package/dist/react/index.js +3396 -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/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 +19 -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/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 +11 -0
- package/dist/svelte/index.js +2903 -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 +2 -0
- package/dist/testing/index.js +2973 -156
- package/dist/testing/ioProviderSimulator.d.ts +41 -0
- package/dist/testing/providerSimulator.d.ts +44 -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/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 +20 -0
- package/dist/vue/index.js +3327 -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/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,741 +69,1627 @@ 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
|
|
@@ -814,500 +1700,1279 @@ var TURN_PROFILE_DEFAULTS = {
|
|
|
814
1700
|
speechThreshold: 0.015,
|
|
815
1701
|
transcriptStabilityMs: 450
|
|
816
1702
|
},
|
|
817
|
-
"long-form": {
|
|
818
|
-
qualityProfile: "general",
|
|
819
|
-
silenceMs: 2200,
|
|
820
|
-
speechThreshold: 0.01,
|
|
821
|
-
transcriptStabilityMs: 1500
|
|
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
|
+
}
|
|
1784
|
+
},
|
|
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}`);
|
|
822
2206
|
}
|
|
2207
|
+
return await response.json();
|
|
823
2208
|
};
|
|
824
|
-
var
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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);
|
|
840
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
|
+
};
|
|
841
2277
|
};
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
var
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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-status.service.ts
|
|
2320
|
+
import { computed as computed8, Injectable as Injectable8, signal as signal8 } from "@angular/core";
|
|
2321
|
+
|
|
2322
|
+
// src/client/providerStatus.ts
|
|
2323
|
+
var fetchVoiceProviderStatus = async (path = "/api/provider-status", options = {}) => {
|
|
2324
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2325
|
+
const response = await fetchImpl(path);
|
|
2326
|
+
if (!response.ok) {
|
|
2327
|
+
throw new Error(`Voice provider status failed: HTTP ${response.status}`);
|
|
2328
|
+
}
|
|
2329
|
+
return await response.json();
|
|
2330
|
+
};
|
|
2331
|
+
var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {}) => {
|
|
2332
|
+
const listeners = new Set;
|
|
2333
|
+
let closed = false;
|
|
2334
|
+
let timer;
|
|
2335
|
+
let snapshot = {
|
|
2336
|
+
error: null,
|
|
2337
|
+
isLoading: false,
|
|
2338
|
+
providers: []
|
|
2339
|
+
};
|
|
2340
|
+
const emit = () => {
|
|
2341
|
+
for (const listener of listeners) {
|
|
2342
|
+
listener();
|
|
2343
|
+
}
|
|
2344
|
+
};
|
|
2345
|
+
const refresh = async () => {
|
|
2346
|
+
if (closed) {
|
|
2347
|
+
return snapshot.providers;
|
|
2348
|
+
}
|
|
2349
|
+
snapshot = {
|
|
2350
|
+
...snapshot,
|
|
2351
|
+
error: null,
|
|
2352
|
+
isLoading: true
|
|
2353
|
+
};
|
|
2354
|
+
emit();
|
|
2355
|
+
try {
|
|
2356
|
+
const providers = await fetchVoiceProviderStatus(path, options);
|
|
2357
|
+
snapshot = {
|
|
2358
|
+
error: null,
|
|
2359
|
+
isLoading: false,
|
|
2360
|
+
providers,
|
|
2361
|
+
updatedAt: Date.now()
|
|
2362
|
+
};
|
|
2363
|
+
emit();
|
|
2364
|
+
return providers;
|
|
2365
|
+
} catch (error) {
|
|
2366
|
+
snapshot = {
|
|
2367
|
+
...snapshot,
|
|
2368
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2369
|
+
isLoading: false
|
|
2370
|
+
};
|
|
2371
|
+
emit();
|
|
2372
|
+
throw error;
|
|
2373
|
+
}
|
|
2374
|
+
};
|
|
2375
|
+
const close = () => {
|
|
2376
|
+
closed = true;
|
|
2377
|
+
if (timer) {
|
|
2378
|
+
clearInterval(timer);
|
|
2379
|
+
timer = undefined;
|
|
2380
|
+
}
|
|
2381
|
+
listeners.clear();
|
|
2382
|
+
};
|
|
2383
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
2384
|
+
timer = setInterval(() => {
|
|
2385
|
+
refresh().catch(() => {});
|
|
2386
|
+
}, options.intervalMs);
|
|
2387
|
+
}
|
|
849
2388
|
return {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
2389
|
+
close,
|
|
2390
|
+
getServerSnapshot: () => snapshot,
|
|
2391
|
+
getSnapshot: () => snapshot,
|
|
2392
|
+
refresh,
|
|
2393
|
+
subscribe: (listener) => {
|
|
2394
|
+
listeners.add(listener);
|
|
2395
|
+
return () => {
|
|
2396
|
+
listeners.delete(listener);
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
};
|
|
2400
|
+
};
|
|
2401
|
+
|
|
2402
|
+
// src/angular/voice-provider-status.service.ts
|
|
2403
|
+
var _dec = [
|
|
2404
|
+
Injectable8({ providedIn: "root" })
|
|
2405
|
+
];
|
|
2406
|
+
var _init = __decoratorStart(undefined);
|
|
2407
|
+
|
|
2408
|
+
class VoiceProviderStatusService {
|
|
2409
|
+
connect(path = "/api/provider-status", options = {}) {
|
|
2410
|
+
const store = createVoiceProviderStatusStore(path, options);
|
|
2411
|
+
const errorSignal = signal8(null);
|
|
2412
|
+
const isLoadingSignal = signal8(false);
|
|
2413
|
+
const providersSignal = signal8([]);
|
|
2414
|
+
const updatedAtSignal = signal8(undefined);
|
|
2415
|
+
const sync = () => {
|
|
2416
|
+
const snapshot = store.getSnapshot();
|
|
2417
|
+
errorSignal.set(snapshot.error);
|
|
2418
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
2419
|
+
providersSignal.set([...snapshot.providers]);
|
|
2420
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
2421
|
+
};
|
|
2422
|
+
const unsubscribe = store.subscribe(sync);
|
|
2423
|
+
sync();
|
|
2424
|
+
store.refresh().catch(() => {});
|
|
2425
|
+
return {
|
|
2426
|
+
close: () => {
|
|
2427
|
+
unsubscribe();
|
|
2428
|
+
store.close();
|
|
2429
|
+
},
|
|
2430
|
+
error: computed8(() => errorSignal()),
|
|
2431
|
+
isLoading: computed8(() => isLoadingSignal()),
|
|
2432
|
+
providers: computed8(() => providersSignal()),
|
|
2433
|
+
refresh: store.refresh,
|
|
2434
|
+
updatedAt: computed8(() => updatedAtSignal())
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
VoiceProviderStatusService = __decorateElement(_init, 0, "VoiceProviderStatusService", _dec, VoiceProviderStatusService);
|
|
2439
|
+
__runInitializers(_init, 1, VoiceProviderStatusService);
|
|
2440
|
+
__decoratorMetadata(_init, VoiceProviderStatusService);
|
|
2441
|
+
let _VoiceProviderStatusService = VoiceProviderStatusService;
|
|
2442
|
+
// src/angular/voice-routing-status.service.ts
|
|
2443
|
+
import { Injectable as Injectable9, signal as signal9 } from "@angular/core";
|
|
2444
|
+
|
|
2445
|
+
// src/client/routingStatus.ts
|
|
2446
|
+
var fetchVoiceRoutingStatus = async (path = "/api/routing/latest", options = {}) => {
|
|
2447
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2448
|
+
const response = await fetchImpl(path);
|
|
2449
|
+
if (!response.ok) {
|
|
2450
|
+
throw new Error(`Voice routing status failed: HTTP ${response.status}`);
|
|
2451
|
+
}
|
|
2452
|
+
return await response.json();
|
|
2453
|
+
};
|
|
2454
|
+
var createVoiceRoutingStatusStore = (path = "/api/routing/latest", options = {}) => {
|
|
2455
|
+
const listeners = new Set;
|
|
2456
|
+
let closed = false;
|
|
2457
|
+
let timer;
|
|
2458
|
+
let snapshot = {
|
|
2459
|
+
decision: null,
|
|
2460
|
+
error: null,
|
|
2461
|
+
isLoading: false
|
|
855
2462
|
};
|
|
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"
|
|
2463
|
+
const emit = () => {
|
|
2464
|
+
for (const listener of listeners) {
|
|
2465
|
+
listener();
|
|
881
2466
|
}
|
|
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"
|
|
2467
|
+
};
|
|
2468
|
+
const refresh = async () => {
|
|
2469
|
+
if (closed) {
|
|
2470
|
+
return snapshot.decision;
|
|
897
2471
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
2472
|
+
snapshot = {
|
|
2473
|
+
...snapshot,
|
|
2474
|
+
error: null,
|
|
2475
|
+
isLoading: true
|
|
2476
|
+
};
|
|
2477
|
+
emit();
|
|
2478
|
+
try {
|
|
2479
|
+
const decision = await fetchVoiceRoutingStatus(path, options);
|
|
2480
|
+
snapshot = {
|
|
2481
|
+
decision,
|
|
2482
|
+
error: null,
|
|
2483
|
+
isLoading: false,
|
|
2484
|
+
updatedAt: Date.now()
|
|
2485
|
+
};
|
|
2486
|
+
emit();
|
|
2487
|
+
return decision;
|
|
2488
|
+
} catch (error) {
|
|
2489
|
+
snapshot = {
|
|
2490
|
+
...snapshot,
|
|
2491
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2492
|
+
isLoading: false
|
|
2493
|
+
};
|
|
2494
|
+
emit();
|
|
2495
|
+
throw error;
|
|
920
2496
|
}
|
|
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"
|
|
2497
|
+
};
|
|
2498
|
+
const close = () => {
|
|
2499
|
+
closed = true;
|
|
2500
|
+
if (timer) {
|
|
2501
|
+
clearInterval(timer);
|
|
2502
|
+
timer = undefined;
|
|
943
2503
|
}
|
|
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
|
|
2504
|
+
listeners.clear();
|
|
2505
|
+
};
|
|
2506
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
2507
|
+
timer = setInterval(() => {
|
|
2508
|
+
refresh().catch(() => {});
|
|
2509
|
+
}, options.intervalMs);
|
|
2510
|
+
}
|
|
2511
|
+
return {
|
|
2512
|
+
close,
|
|
2513
|
+
getServerSnapshot: () => snapshot,
|
|
2514
|
+
getSnapshot: () => snapshot,
|
|
2515
|
+
refresh,
|
|
2516
|
+
subscribe: (listener) => {
|
|
2517
|
+
listeners.add(listener);
|
|
2518
|
+
return () => {
|
|
2519
|
+
listeners.delete(listener);
|
|
2520
|
+
};
|
|
969
2521
|
}
|
|
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
|
-
|
|
2522
|
+
};
|
|
2523
|
+
};
|
|
2524
|
+
|
|
2525
|
+
// src/angular/voice-routing-status.service.ts
|
|
2526
|
+
var _dec = [
|
|
2527
|
+
Injectable9({ providedIn: "root" })
|
|
2528
|
+
];
|
|
2529
|
+
var _init = __decoratorStart(undefined);
|
|
2530
|
+
|
|
2531
|
+
class VoiceRoutingStatusService {
|
|
2532
|
+
connect(path = "/api/routing/latest", options = {}) {
|
|
2533
|
+
const store = createVoiceRoutingStatusStore(path, options);
|
|
2534
|
+
const decisionSignal = signal9(null);
|
|
2535
|
+
const errorSignal = signal9(null);
|
|
2536
|
+
const isLoadingSignal = signal9(false);
|
|
2537
|
+
const updatedAtSignal = signal9(undefined);
|
|
2538
|
+
const sync = () => {
|
|
2539
|
+
const snapshot = store.getSnapshot();
|
|
2540
|
+
decisionSignal.set(snapshot.decision);
|
|
2541
|
+
errorSignal.set(snapshot.error);
|
|
2542
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
2543
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
2544
|
+
};
|
|
2545
|
+
const unsubscribe = store.subscribe(sync);
|
|
2546
|
+
sync();
|
|
2547
|
+
store.refresh().catch(() => {});
|
|
2548
|
+
return {
|
|
2549
|
+
close: () => {
|
|
2550
|
+
unsubscribe();
|
|
2551
|
+
store.close();
|
|
2552
|
+
},
|
|
2553
|
+
decision: decisionSignal.asReadonly(),
|
|
2554
|
+
error: errorSignal.asReadonly(),
|
|
2555
|
+
isLoading: isLoadingSignal.asReadonly(),
|
|
2556
|
+
refresh: store.refresh,
|
|
2557
|
+
updatedAt: updatedAtSignal.asReadonly()
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
VoiceRoutingStatusService = __decorateElement(_init, 0, "VoiceRoutingStatusService", _dec, VoiceRoutingStatusService);
|
|
2562
|
+
__runInitializers(_init, 1, VoiceRoutingStatusService);
|
|
2563
|
+
__decoratorMetadata(_init, VoiceRoutingStatusService);
|
|
2564
|
+
let _VoiceRoutingStatusService = VoiceRoutingStatusService;
|
|
2565
|
+
// src/angular/voice-trace-timeline.service.ts
|
|
2566
|
+
import { computed as computed9, Injectable as Injectable10, signal as signal10 } from "@angular/core";
|
|
2567
|
+
|
|
2568
|
+
// src/client/traceTimeline.ts
|
|
2569
|
+
var fetchVoiceTraceTimeline = async (path = "/api/voice-traces", options = {}) => {
|
|
2570
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2571
|
+
const response = await fetchImpl(path);
|
|
2572
|
+
if (!response.ok) {
|
|
2573
|
+
throw new Error(`Voice trace timeline failed: HTTP ${response.status}`);
|
|
2574
|
+
}
|
|
2575
|
+
return await response.json();
|
|
2576
|
+
};
|
|
2577
|
+
var createVoiceTraceTimelineStore = (path = "/api/voice-traces", options = {}) => {
|
|
2578
|
+
const listeners = new Set;
|
|
2579
|
+
let closed = false;
|
|
2580
|
+
let timer;
|
|
2581
|
+
let snapshot = {
|
|
2582
|
+
error: null,
|
|
2583
|
+
isLoading: false,
|
|
2584
|
+
report: null
|
|
2585
|
+
};
|
|
2586
|
+
const emit = () => {
|
|
2587
|
+
for (const listener of listeners) {
|
|
2588
|
+
listener();
|
|
995
2589
|
}
|
|
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
|
|
2590
|
+
};
|
|
2591
|
+
const refresh = async () => {
|
|
2592
|
+
if (closed) {
|
|
2593
|
+
return snapshot.report;
|
|
1021
2594
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
2595
|
+
snapshot = {
|
|
2596
|
+
...snapshot,
|
|
2597
|
+
error: null,
|
|
2598
|
+
isLoading: true
|
|
2599
|
+
};
|
|
2600
|
+
emit();
|
|
2601
|
+
try {
|
|
2602
|
+
const report = await fetchVoiceTraceTimeline(path, options);
|
|
2603
|
+
snapshot = {
|
|
2604
|
+
error: null,
|
|
2605
|
+
isLoading: false,
|
|
2606
|
+
report,
|
|
2607
|
+
updatedAt: Date.now()
|
|
2608
|
+
};
|
|
2609
|
+
emit();
|
|
2610
|
+
return report;
|
|
2611
|
+
} catch (error) {
|
|
2612
|
+
snapshot = {
|
|
2613
|
+
...snapshot,
|
|
2614
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2615
|
+
isLoading: false
|
|
2616
|
+
};
|
|
2617
|
+
emit();
|
|
2618
|
+
throw error;
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
const close = () => {
|
|
2622
|
+
closed = true;
|
|
2623
|
+
if (timer) {
|
|
2624
|
+
clearInterval(timer);
|
|
2625
|
+
timer = undefined;
|
|
1044
2626
|
}
|
|
2627
|
+
listeners.clear();
|
|
2628
|
+
};
|
|
2629
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
2630
|
+
timer = setInterval(() => {
|
|
2631
|
+
refresh().catch(() => {});
|
|
2632
|
+
}, options.intervalMs);
|
|
1045
2633
|
}
|
|
1046
|
-
};
|
|
1047
|
-
var resolveVoiceRuntimePreset = (name = "default") => {
|
|
1048
|
-
const preset = PRESET_INPUTS[name];
|
|
1049
2634
|
return {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
turnDetection: resolveTurnDetectionConfig(preset.turnDetection)
|
|
2635
|
+
close,
|
|
2636
|
+
getServerSnapshot: () => snapshot,
|
|
2637
|
+
getSnapshot: () => snapshot,
|
|
2638
|
+
refresh,
|
|
2639
|
+
subscribe: (listener) => {
|
|
2640
|
+
listeners.add(listener);
|
|
2641
|
+
return () => {
|
|
2642
|
+
listeners.delete(listener);
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
1061
2645
|
};
|
|
1062
2646
|
};
|
|
1063
2647
|
|
|
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
|
-
|
|
2648
|
+
// src/angular/voice-trace-timeline.service.ts
|
|
2649
|
+
var _dec = [
|
|
2650
|
+
Injectable10({ providedIn: "root" })
|
|
2651
|
+
];
|
|
2652
|
+
var _init = __decoratorStart(undefined);
|
|
2653
|
+
|
|
2654
|
+
class VoiceTraceTimelineService {
|
|
2655
|
+
connect(path = "/api/voice-traces", options = {}) {
|
|
2656
|
+
const store = createVoiceTraceTimelineStore(path, options);
|
|
2657
|
+
const errorSignal = signal10(null);
|
|
2658
|
+
const isLoadingSignal = signal10(false);
|
|
2659
|
+
const reportSignal = signal10(null);
|
|
2660
|
+
const updatedAtSignal = signal10(undefined);
|
|
2661
|
+
const sync = () => {
|
|
2662
|
+
const snapshot = store.getSnapshot();
|
|
2663
|
+
errorSignal.set(snapshot.error);
|
|
2664
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
2665
|
+
reportSignal.set(snapshot.report);
|
|
2666
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
2667
|
+
};
|
|
2668
|
+
const unsubscribe = store.subscribe(sync);
|
|
2669
|
+
sync();
|
|
2670
|
+
store.refresh().catch(() => {});
|
|
2671
|
+
return {
|
|
2672
|
+
close: () => {
|
|
2673
|
+
unsubscribe();
|
|
2674
|
+
store.close();
|
|
2675
|
+
},
|
|
2676
|
+
error: computed9(() => errorSignal()),
|
|
2677
|
+
isLoading: computed9(() => isLoadingSignal()),
|
|
2678
|
+
refresh: store.refresh,
|
|
2679
|
+
report: computed9(() => reportSignal()),
|
|
2680
|
+
updatedAt: computed9(() => updatedAtSignal())
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
VoiceTraceTimelineService = __decorateElement(_init, 0, "VoiceTraceTimelineService", _dec, VoiceTraceTimelineService);
|
|
2685
|
+
__runInitializers(_init, 1, VoiceTraceTimelineService);
|
|
2686
|
+
__decoratorMetadata(_init, VoiceTraceTimelineService);
|
|
2687
|
+
let _VoiceTraceTimelineService = VoiceTraceTimelineService;
|
|
2688
|
+
// src/angular/voice-turn-latency.service.ts
|
|
2689
|
+
import { computed as computed10, Injectable as Injectable11, signal as signal11 } from "@angular/core";
|
|
2690
|
+
|
|
2691
|
+
// src/client/turnLatency.ts
|
|
2692
|
+
var fetchVoiceTurnLatency = async (path = "/api/turn-latency", options = {}) => {
|
|
2693
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2694
|
+
const response = await fetchImpl(path);
|
|
2695
|
+
if (!response.ok) {
|
|
2696
|
+
throw new Error(`Voice turn latency failed: HTTP ${response.status}`);
|
|
2697
|
+
}
|
|
2698
|
+
return await response.json();
|
|
2699
|
+
};
|
|
2700
|
+
var runVoiceTurnLatencyProof = async (path, options = {}) => {
|
|
2701
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2702
|
+
const response = await fetchImpl(path, { method: "POST" });
|
|
2703
|
+
if (!response.ok) {
|
|
2704
|
+
throw new Error(`Voice turn latency proof failed: HTTP ${response.status}`);
|
|
2705
|
+
}
|
|
2706
|
+
return response.json();
|
|
2707
|
+
};
|
|
2708
|
+
var createVoiceTurnLatencyStore = (path = "/api/turn-latency", options = {}) => {
|
|
2709
|
+
const listeners = new Set;
|
|
2710
|
+
let closed = false;
|
|
2711
|
+
let timer;
|
|
2712
|
+
let snapshot = {
|
|
2713
|
+
error: null,
|
|
2714
|
+
isLoading: false
|
|
2715
|
+
};
|
|
2716
|
+
const emit = () => {
|
|
2717
|
+
for (const listener of listeners) {
|
|
2718
|
+
listener();
|
|
1090
2719
|
}
|
|
1091
2720
|
};
|
|
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
|
|
2721
|
+
const refresh = async () => {
|
|
2722
|
+
if (closed) {
|
|
2723
|
+
return snapshot.report;
|
|
2724
|
+
}
|
|
2725
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
2726
|
+
emit();
|
|
2727
|
+
try {
|
|
2728
|
+
const report = await fetchVoiceTurnLatency(path, options);
|
|
2729
|
+
snapshot = {
|
|
2730
|
+
error: null,
|
|
2731
|
+
isLoading: false,
|
|
2732
|
+
report,
|
|
2733
|
+
updatedAt: Date.now()
|
|
1111
2734
|
};
|
|
2735
|
+
emit();
|
|
2736
|
+
return report;
|
|
2737
|
+
} catch (error) {
|
|
2738
|
+
snapshot = {
|
|
2739
|
+
...snapshot,
|
|
2740
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2741
|
+
isLoading: false
|
|
2742
|
+
};
|
|
2743
|
+
emit();
|
|
2744
|
+
throw error;
|
|
1112
2745
|
}
|
|
1113
|
-
notify();
|
|
1114
2746
|
};
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
2747
|
+
const runProof = async () => {
|
|
2748
|
+
if (!options.proofPath) {
|
|
2749
|
+
throw new Error("Voice turn latency proof path is not configured.");
|
|
2750
|
+
}
|
|
2751
|
+
snapshot = { ...snapshot, error: null, isLoading: true };
|
|
2752
|
+
emit();
|
|
2753
|
+
try {
|
|
2754
|
+
await runVoiceTurnLatencyProof(options.proofPath, options);
|
|
2755
|
+
return await refresh();
|
|
2756
|
+
} catch (error) {
|
|
2757
|
+
snapshot = {
|
|
2758
|
+
...snapshot,
|
|
2759
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2760
|
+
isLoading: false
|
|
2761
|
+
};
|
|
2762
|
+
emit();
|
|
2763
|
+
throw error;
|
|
2764
|
+
}
|
|
2765
|
+
};
|
|
2766
|
+
const close = () => {
|
|
2767
|
+
closed = true;
|
|
2768
|
+
if (timer) {
|
|
2769
|
+
clearInterval(timer);
|
|
2770
|
+
timer = undefined;
|
|
2771
|
+
}
|
|
2772
|
+
listeners.clear();
|
|
2773
|
+
};
|
|
2774
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
2775
|
+
timer = setInterval(() => {
|
|
2776
|
+
refresh().catch(() => {});
|
|
2777
|
+
}, options.intervalMs);
|
|
2778
|
+
}
|
|
2779
|
+
return {
|
|
2780
|
+
close,
|
|
2781
|
+
getServerSnapshot: () => snapshot,
|
|
2782
|
+
getSnapshot: () => snapshot,
|
|
2783
|
+
refresh,
|
|
2784
|
+
runProof,
|
|
2785
|
+
subscribe: (listener) => {
|
|
2786
|
+
listeners.add(listener);
|
|
2787
|
+
return () => {
|
|
2788
|
+
listeners.delete(listener);
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
};
|
|
2793
|
+
|
|
2794
|
+
// src/angular/voice-turn-latency.service.ts
|
|
2795
|
+
var _dec = [
|
|
2796
|
+
Injectable11({ providedIn: "root" })
|
|
2797
|
+
];
|
|
2798
|
+
var _init = __decoratorStart(undefined);
|
|
2799
|
+
|
|
2800
|
+
class VoiceTurnLatencyService {
|
|
2801
|
+
connect(path = "/api/turn-latency", options = {}) {
|
|
2802
|
+
const store = createVoiceTurnLatencyStore(path, options);
|
|
2803
|
+
const errorSignal = signal11(null);
|
|
2804
|
+
const isLoadingSignal = signal11(false);
|
|
2805
|
+
const reportSignal = signal11(undefined);
|
|
2806
|
+
const updatedAtSignal = signal11(undefined);
|
|
2807
|
+
const sync = () => {
|
|
2808
|
+
const snapshot = store.getSnapshot();
|
|
2809
|
+
errorSignal.set(snapshot.error);
|
|
2810
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
2811
|
+
reportSignal.set(snapshot.report);
|
|
2812
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
2813
|
+
};
|
|
2814
|
+
const unsubscribe = store.subscribe(sync);
|
|
2815
|
+
sync();
|
|
2816
|
+
store.refresh().catch(() => {});
|
|
2817
|
+
return {
|
|
2818
|
+
close: () => {
|
|
2819
|
+
unsubscribe();
|
|
2820
|
+
store.close();
|
|
2821
|
+
},
|
|
2822
|
+
error: computed10(() => errorSignal()),
|
|
2823
|
+
isLoading: computed10(() => isLoadingSignal()),
|
|
2824
|
+
refresh: store.refresh,
|
|
2825
|
+
report: computed10(() => reportSignal()),
|
|
2826
|
+
runProof: store.runProof,
|
|
2827
|
+
updatedAt: computed10(() => updatedAtSignal())
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
VoiceTurnLatencyService = __decorateElement(_init, 0, "VoiceTurnLatencyService", _dec, VoiceTurnLatencyService);
|
|
2832
|
+
__runInitializers(_init, 1, VoiceTurnLatencyService);
|
|
2833
|
+
__decoratorMetadata(_init, VoiceTurnLatencyService);
|
|
2834
|
+
let _VoiceTurnLatencyService = VoiceTurnLatencyService;
|
|
2835
|
+
// src/angular/voice-turn-quality.service.ts
|
|
2836
|
+
import { computed as computed11, Injectable as Injectable12, signal as signal12 } from "@angular/core";
|
|
2837
|
+
|
|
2838
|
+
// src/client/turnQuality.ts
|
|
2839
|
+
var fetchVoiceTurnQuality = async (path = "/api/turn-quality", options = {}) => {
|
|
2840
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2841
|
+
const response = await fetchImpl(path);
|
|
2842
|
+
if (!response.ok) {
|
|
2843
|
+
throw new Error(`Voice turn quality failed: HTTP ${response.status}`);
|
|
2844
|
+
}
|
|
2845
|
+
return await response.json();
|
|
2846
|
+
};
|
|
2847
|
+
var createVoiceTurnQualityStore = (path = "/api/turn-quality", options = {}) => {
|
|
2848
|
+
const listeners = new Set;
|
|
2849
|
+
let closed = false;
|
|
2850
|
+
let timer;
|
|
2851
|
+
let snapshot = {
|
|
2852
|
+
error: null,
|
|
2853
|
+
isLoading: false
|
|
2854
|
+
};
|
|
2855
|
+
const emit = () => {
|
|
2856
|
+
for (const listener of listeners) {
|
|
2857
|
+
listener();
|
|
1120
2858
|
}
|
|
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
2859
|
};
|
|
1138
|
-
const
|
|
1139
|
-
if (
|
|
1140
|
-
return;
|
|
2860
|
+
const refresh = async () => {
|
|
2861
|
+
if (closed) {
|
|
2862
|
+
return snapshot.report;
|
|
1141
2863
|
}
|
|
2864
|
+
snapshot = {
|
|
2865
|
+
...snapshot,
|
|
2866
|
+
error: null,
|
|
2867
|
+
isLoading: true
|
|
2868
|
+
};
|
|
2869
|
+
emit();
|
|
1142
2870
|
try {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
state = {
|
|
1150
|
-
...state,
|
|
1151
|
-
isRecording: true
|
|
2871
|
+
const report = await fetchVoiceTurnQuality(path, options);
|
|
2872
|
+
snapshot = {
|
|
2873
|
+
error: null,
|
|
2874
|
+
isLoading: false,
|
|
2875
|
+
report,
|
|
2876
|
+
updatedAt: Date.now()
|
|
1152
2877
|
};
|
|
1153
|
-
|
|
2878
|
+
emit();
|
|
2879
|
+
return report;
|
|
1154
2880
|
} catch (error) {
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
recordingError: error instanceof Error ? error.message : String(error)
|
|
2881
|
+
snapshot = {
|
|
2882
|
+
...snapshot,
|
|
2883
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2884
|
+
isLoading: false
|
|
1160
2885
|
};
|
|
1161
|
-
|
|
2886
|
+
emit();
|
|
1162
2887
|
throw error;
|
|
1163
2888
|
}
|
|
1164
2889
|
};
|
|
1165
2890
|
const close = () => {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
2891
|
+
closed = true;
|
|
2892
|
+
if (timer) {
|
|
2893
|
+
clearInterval(timer);
|
|
2894
|
+
timer = undefined;
|
|
2895
|
+
}
|
|
2896
|
+
listeners.clear();
|
|
1169
2897
|
};
|
|
2898
|
+
if (options.intervalMs && options.intervalMs > 0) {
|
|
2899
|
+
timer = setInterval(() => {
|
|
2900
|
+
refresh().catch(() => {});
|
|
2901
|
+
}, options.intervalMs);
|
|
2902
|
+
}
|
|
1170
2903
|
return {
|
|
1171
|
-
bindHTMX(bindingOptions) {
|
|
1172
|
-
return bindVoiceHTMX(stream, bindingOptions);
|
|
1173
|
-
},
|
|
1174
2904
|
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);
|
|
2905
|
+
getServerSnapshot: () => snapshot,
|
|
2906
|
+
getSnapshot: () => snapshot,
|
|
2907
|
+
refresh,
|
|
2908
|
+
subscribe: (listener) => {
|
|
2909
|
+
listeners.add(listener);
|
|
1207
2910
|
return () => {
|
|
1208
|
-
|
|
2911
|
+
listeners.delete(listener);
|
|
1209
2912
|
};
|
|
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
2913
|
}
|
|
1227
2914
|
};
|
|
1228
2915
|
};
|
|
1229
2916
|
|
|
1230
|
-
// src/angular/voice-
|
|
2917
|
+
// src/angular/voice-turn-quality.service.ts
|
|
1231
2918
|
var _dec = [
|
|
1232
|
-
|
|
2919
|
+
Injectable12({ providedIn: "root" })
|
|
1233
2920
|
];
|
|
1234
2921
|
var _init = __decoratorStart(undefined);
|
|
1235
2922
|
|
|
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([]);
|
|
2923
|
+
class VoiceTurnQualityService {
|
|
2924
|
+
connect(path = "/api/turn-quality", options = {}) {
|
|
2925
|
+
const store = createVoiceTurnQualityStore(path, options);
|
|
2926
|
+
const errorSignal = signal12(null);
|
|
2927
|
+
const isLoadingSignal = signal12(false);
|
|
2928
|
+
const reportSignal = signal12(undefined);
|
|
2929
|
+
const updatedAtSignal = signal12(undefined);
|
|
1249
2930
|
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]);
|
|
2931
|
+
const snapshot = store.getSnapshot();
|
|
2932
|
+
errorSignal.set(snapshot.error);
|
|
2933
|
+
isLoadingSignal.set(snapshot.isLoading);
|
|
2934
|
+
reportSignal.set(snapshot.report);
|
|
2935
|
+
updatedAtSignal.set(snapshot.updatedAt);
|
|
1260
2936
|
};
|
|
1261
|
-
const unsubscribe =
|
|
2937
|
+
const unsubscribe = store.subscribe(sync);
|
|
1262
2938
|
sync();
|
|
2939
|
+
store.refresh().catch(() => {});
|
|
1263
2940
|
return {
|
|
1264
|
-
assistantAudio: computed2(() => assistantAudioSignal()),
|
|
1265
|
-
assistantTexts: computed2(() => assistantTextsSignal()),
|
|
1266
|
-
bindHTMX: controller.bindHTMX,
|
|
1267
2941
|
close: () => {
|
|
1268
2942
|
unsubscribe();
|
|
1269
|
-
|
|
2943
|
+
store.close();
|
|
1270
2944
|
},
|
|
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())
|
|
2945
|
+
error: computed11(() => errorSignal()),
|
|
2946
|
+
isLoading: computed11(() => isLoadingSignal()),
|
|
2947
|
+
refresh: store.refresh,
|
|
2948
|
+
report: computed11(() => reportSignal()),
|
|
2949
|
+
updatedAt: computed11(() => updatedAtSignal())
|
|
1284
2950
|
};
|
|
1285
2951
|
}
|
|
1286
2952
|
}
|
|
1287
|
-
|
|
1288
|
-
__runInitializers(_init, 1,
|
|
1289
|
-
__decoratorMetadata(_init,
|
|
1290
|
-
let
|
|
1291
|
-
// src/angular/voice-
|
|
1292
|
-
import { computed as
|
|
2953
|
+
VoiceTurnQualityService = __decorateElement(_init, 0, "VoiceTurnQualityService", _dec, VoiceTurnQualityService);
|
|
2954
|
+
__runInitializers(_init, 1, VoiceTurnQualityService);
|
|
2955
|
+
__decoratorMetadata(_init, VoiceTurnQualityService);
|
|
2956
|
+
let _VoiceTurnQualityService = VoiceTurnQualityService;
|
|
2957
|
+
// src/angular/voice-workflow-status.service.ts
|
|
2958
|
+
import { computed as computed12, Injectable as Injectable13, signal as signal13 } from "@angular/core";
|
|
1293
2959
|
|
|
1294
|
-
// src/client/
|
|
1295
|
-
var
|
|
2960
|
+
// src/client/workflowStatus.ts
|
|
2961
|
+
var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
|
|
1296
2962
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1297
2963
|
const response = await fetchImpl(path);
|
|
1298
2964
|
if (!response.ok) {
|
|
1299
|
-
throw new Error(`Voice
|
|
2965
|
+
throw new Error(`Voice workflow status failed: HTTP ${response.status}`);
|
|
1300
2966
|
}
|
|
1301
2967
|
return await response.json();
|
|
1302
2968
|
};
|
|
1303
|
-
var
|
|
2969
|
+
var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options = {}) => {
|
|
1304
2970
|
const listeners = new Set;
|
|
1305
2971
|
let closed = false;
|
|
1306
2972
|
let timer;
|
|
1307
2973
|
let snapshot = {
|
|
1308
2974
|
error: null,
|
|
1309
|
-
isLoading: false
|
|
1310
|
-
providers: []
|
|
2975
|
+
isLoading: false
|
|
1311
2976
|
};
|
|
1312
2977
|
const emit = () => {
|
|
1313
2978
|
for (const listener of listeners) {
|
|
@@ -1316,7 +2981,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
|
|
|
1316
2981
|
};
|
|
1317
2982
|
const refresh = async () => {
|
|
1318
2983
|
if (closed) {
|
|
1319
|
-
return snapshot.
|
|
2984
|
+
return snapshot.report;
|
|
1320
2985
|
}
|
|
1321
2986
|
snapshot = {
|
|
1322
2987
|
...snapshot,
|
|
@@ -1325,15 +2990,15 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
|
|
|
1325
2990
|
};
|
|
1326
2991
|
emit();
|
|
1327
2992
|
try {
|
|
1328
|
-
const
|
|
2993
|
+
const report = await fetchVoiceWorkflowStatus(path, options);
|
|
1329
2994
|
snapshot = {
|
|
1330
2995
|
error: null,
|
|
1331
2996
|
isLoading: false,
|
|
1332
|
-
|
|
2997
|
+
report,
|
|
1333
2998
|
updatedAt: Date.now()
|
|
1334
2999
|
};
|
|
1335
3000
|
emit();
|
|
1336
|
-
return
|
|
3001
|
+
return report;
|
|
1337
3002
|
} catch (error) {
|
|
1338
3003
|
snapshot = {
|
|
1339
3004
|
...snapshot,
|
|
@@ -1352,7 +3017,7 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
|
|
|
1352
3017
|
}
|
|
1353
3018
|
listeners.clear();
|
|
1354
3019
|
};
|
|
1355
|
-
if (options.intervalMs && options.intervalMs > 0) {
|
|
3020
|
+
if (typeof window !== "undefined" && options.intervalMs && options.intervalMs > 0) {
|
|
1356
3021
|
timer = setInterval(() => {
|
|
1357
3022
|
refresh().catch(() => {});
|
|
1358
3023
|
}, options.intervalMs);
|
|
@@ -1371,48 +3036,349 @@ var createVoiceProviderStatusStore = (path = "/api/provider-status", options = {
|
|
|
1371
3036
|
};
|
|
1372
3037
|
};
|
|
1373
3038
|
|
|
1374
|
-
// src/angular/voice-
|
|
3039
|
+
// src/angular/voice-workflow-status.service.ts
|
|
1375
3040
|
var _dec = [
|
|
1376
|
-
|
|
3041
|
+
Injectable13({ providedIn: "root" })
|
|
1377
3042
|
];
|
|
1378
3043
|
var _init = __decoratorStart(undefined);
|
|
1379
3044
|
|
|
1380
|
-
class
|
|
1381
|
-
connect(path = "/
|
|
1382
|
-
const store =
|
|
1383
|
-
const errorSignal =
|
|
1384
|
-
const isLoadingSignal =
|
|
1385
|
-
const
|
|
1386
|
-
const updatedAtSignal =
|
|
3045
|
+
class VoiceWorkflowStatusService {
|
|
3046
|
+
connect(path = "/evals/scenarios/json", options = {}) {
|
|
3047
|
+
const store = createVoiceWorkflowStatusStore(path, options);
|
|
3048
|
+
const errorSignal = signal13(null);
|
|
3049
|
+
const isLoadingSignal = signal13(false);
|
|
3050
|
+
const reportSignal = signal13(undefined);
|
|
3051
|
+
const updatedAtSignal = signal13(undefined);
|
|
1387
3052
|
const sync = () => {
|
|
1388
3053
|
const snapshot = store.getSnapshot();
|
|
1389
3054
|
errorSignal.set(snapshot.error);
|
|
1390
3055
|
isLoadingSignal.set(snapshot.isLoading);
|
|
1391
|
-
|
|
3056
|
+
reportSignal.set(snapshot.report);
|
|
1392
3057
|
updatedAtSignal.set(snapshot.updatedAt);
|
|
1393
3058
|
};
|
|
1394
3059
|
const unsubscribe = store.subscribe(sync);
|
|
1395
3060
|
sync();
|
|
1396
|
-
|
|
3061
|
+
if (typeof window !== "undefined") {
|
|
3062
|
+
store.refresh().catch(() => {});
|
|
3063
|
+
}
|
|
1397
3064
|
return {
|
|
1398
3065
|
close: () => {
|
|
1399
3066
|
unsubscribe();
|
|
1400
3067
|
store.close();
|
|
1401
3068
|
},
|
|
1402
|
-
error:
|
|
1403
|
-
isLoading:
|
|
1404
|
-
providers: computed3(() => providersSignal()),
|
|
3069
|
+
error: computed12(() => errorSignal()),
|
|
3070
|
+
isLoading: computed12(() => isLoadingSignal()),
|
|
1405
3071
|
refresh: store.refresh,
|
|
1406
|
-
|
|
3072
|
+
report: computed12(() => reportSignal()),
|
|
3073
|
+
updatedAt: computed12(() => updatedAtSignal())
|
|
1407
3074
|
};
|
|
1408
3075
|
}
|
|
1409
3076
|
}
|
|
1410
|
-
|
|
1411
|
-
__runInitializers(_init, 1,
|
|
1412
|
-
__decoratorMetadata(_init,
|
|
1413
|
-
let
|
|
3077
|
+
VoiceWorkflowStatusService = __decorateElement(_init, 0, "VoiceWorkflowStatusService", _dec, VoiceWorkflowStatusService);
|
|
3078
|
+
__runInitializers(_init, 1, VoiceWorkflowStatusService);
|
|
3079
|
+
__decoratorMetadata(_init, VoiceWorkflowStatusService);
|
|
3080
|
+
let _VoiceWorkflowStatusService = VoiceWorkflowStatusService;
|
|
3081
|
+
// src/angular/voice-delivery-runtime.component.ts
|
|
3082
|
+
import { Component, Input, signal as signal14 } from "@angular/core";
|
|
3083
|
+
|
|
3084
|
+
// src/client/deliveryRuntimeWidget.ts
|
|
3085
|
+
var DEFAULT_TITLE = "Voice Delivery Runtime";
|
|
3086
|
+
var DEFAULT_DESCRIPTION = "Audit and trace delivery worker health from your AbsoluteJS voice app.";
|
|
3087
|
+
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3088
|
+
var createSurface = (id, summary) => {
|
|
3089
|
+
if (!summary) {
|
|
3090
|
+
return {
|
|
3091
|
+
deadLettered: 0,
|
|
3092
|
+
detail: "Worker disabled",
|
|
3093
|
+
failed: 0,
|
|
3094
|
+
id,
|
|
3095
|
+
label: id === "audit" ? "Audit delivery" : "Trace delivery",
|
|
3096
|
+
pending: 0,
|
|
3097
|
+
status: "disabled",
|
|
3098
|
+
total: 0
|
|
3099
|
+
};
|
|
3100
|
+
}
|
|
3101
|
+
const blocked = summary.failed + summary.deadLettered;
|
|
3102
|
+
return {
|
|
3103
|
+
deadLettered: summary.deadLettered,
|
|
3104
|
+
detail: `${summary.delivered}/${summary.total} delivered, ${summary.pending} pending`,
|
|
3105
|
+
failed: summary.failed,
|
|
3106
|
+
id,
|
|
3107
|
+
label: id === "audit" ? "Audit delivery" : "Trace delivery",
|
|
3108
|
+
pending: summary.pending,
|
|
3109
|
+
status: blocked > 0 ? "warn" : "pass",
|
|
3110
|
+
total: summary.total
|
|
3111
|
+
};
|
|
3112
|
+
};
|
|
3113
|
+
var createVoiceDeliveryRuntimeViewModel = (snapshot, options = {}) => {
|
|
3114
|
+
const report = snapshot.report;
|
|
3115
|
+
const surfaces = [
|
|
3116
|
+
createSurface("audit", report?.summary.audit),
|
|
3117
|
+
createSurface("trace", report?.summary.trace)
|
|
3118
|
+
];
|
|
3119
|
+
const hasWarnings = surfaces.some((surface) => surface.status === "warn");
|
|
3120
|
+
return {
|
|
3121
|
+
description: options.description ?? DEFAULT_DESCRIPTION,
|
|
3122
|
+
error: snapshot.error,
|
|
3123
|
+
actionError: snapshot.actionError,
|
|
3124
|
+
actionStatus: snapshot.actionStatus,
|
|
3125
|
+
isLoading: snapshot.isLoading,
|
|
3126
|
+
isRunning: Boolean(report?.isRunning),
|
|
3127
|
+
label: snapshot.error ? "Unavailable" : report ? report.isRunning ? "Running" : "Stopped" : "Checking",
|
|
3128
|
+
status: snapshot.error ? "error" : report ? hasWarnings ? "warn" : "pass" : "loading",
|
|
3129
|
+
surfaces,
|
|
3130
|
+
title: options.title ?? DEFAULT_TITLE,
|
|
3131
|
+
updatedAt: snapshot.updatedAt
|
|
3132
|
+
};
|
|
3133
|
+
};
|
|
3134
|
+
var renderVoiceDeliveryRuntimeHTML = (snapshot, options = {}) => {
|
|
3135
|
+
const model = createVoiceDeliveryRuntimeViewModel(snapshot, options);
|
|
3136
|
+
const surfaces = model.surfaces.map((surface) => `<li class="absolute-voice-delivery-runtime__surface absolute-voice-delivery-runtime__surface--${escapeHtml(surface.status)}">
|
|
3137
|
+
<span>${escapeHtml(surface.label)}</span>
|
|
3138
|
+
<strong>${escapeHtml(surface.detail)}</strong>
|
|
3139
|
+
<small>${String(surface.failed)} failed · ${String(surface.deadLettered)} dead-lettered</small>
|
|
3140
|
+
</li>`).join("");
|
|
3141
|
+
const actions = options.includeActions === false ? "" : `<div class="absolute-voice-delivery-runtime__actions">
|
|
3142
|
+
<button type="button" data-absolute-voice-delivery-runtime-action="tick">${model.actionStatus === "running" ? "Working..." : "Tick workers"}</button>
|
|
3143
|
+
<button type="button" data-absolute-voice-delivery-runtime-action="requeue-dead-letters"${model.surfaces.some((surface) => surface.deadLettered > 0) ? "" : " disabled"}>Requeue dead letters</button>
|
|
3144
|
+
</div>`;
|
|
3145
|
+
const actionError = model.actionError ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml(model.actionError)}</p>` : "";
|
|
3146
|
+
return `<section class="absolute-voice-delivery-runtime absolute-voice-delivery-runtime--${escapeHtml(model.status)}">
|
|
3147
|
+
<header class="absolute-voice-delivery-runtime__header">
|
|
3148
|
+
<span class="absolute-voice-delivery-runtime__eyebrow">${escapeHtml(model.title)}</span>
|
|
3149
|
+
<strong class="absolute-voice-delivery-runtime__label">${escapeHtml(model.label)}</strong>
|
|
3150
|
+
</header>
|
|
3151
|
+
<p class="absolute-voice-delivery-runtime__description">${escapeHtml(model.description)}</p>
|
|
3152
|
+
<ul class="absolute-voice-delivery-runtime__surfaces">${surfaces}</ul>
|
|
3153
|
+
${actions}
|
|
3154
|
+
${actionError}
|
|
3155
|
+
${model.error ? `<p class="absolute-voice-delivery-runtime__error">${escapeHtml(model.error)}</p>` : ""}
|
|
3156
|
+
</section>`;
|
|
3157
|
+
};
|
|
3158
|
+
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}`;
|
|
3159
|
+
var mountVoiceDeliveryRuntime = (element, path = "/api/voice-delivery-runtime", options = {}) => {
|
|
3160
|
+
const store = createVoiceDeliveryRuntimeStore(path, options);
|
|
3161
|
+
const render = () => {
|
|
3162
|
+
element.innerHTML = renderVoiceDeliveryRuntimeHTML(store.getSnapshot(), options);
|
|
3163
|
+
};
|
|
3164
|
+
const unsubscribe = store.subscribe(render);
|
|
3165
|
+
const handleClick = (event) => {
|
|
3166
|
+
const target = event.target;
|
|
3167
|
+
if (!(target instanceof Element)) {
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
const action = target.closest("[data-absolute-voice-delivery-runtime-action]");
|
|
3171
|
+
const actionName = action?.getAttribute("data-absolute-voice-delivery-runtime-action");
|
|
3172
|
+
if (actionName === "tick") {
|
|
3173
|
+
store.tick().catch(() => {});
|
|
3174
|
+
}
|
|
3175
|
+
if (actionName === "requeue-dead-letters") {
|
|
3176
|
+
store.requeueDeadLetters().catch(() => {});
|
|
3177
|
+
}
|
|
3178
|
+
};
|
|
3179
|
+
element.addEventListener?.("click", handleClick);
|
|
3180
|
+
render();
|
|
3181
|
+
store.refresh().catch(() => {});
|
|
3182
|
+
return {
|
|
3183
|
+
close: () => {
|
|
3184
|
+
element.removeEventListener?.("click", handleClick);
|
|
3185
|
+
unsubscribe();
|
|
3186
|
+
store.close();
|
|
3187
|
+
},
|
|
3188
|
+
refresh: store.refresh
|
|
3189
|
+
};
|
|
3190
|
+
};
|
|
3191
|
+
var defineVoiceDeliveryRuntimeElement = (tagName = "absolute-voice-delivery-runtime") => {
|
|
3192
|
+
if (typeof window === "undefined" || typeof customElements === "undefined" || customElements.get(tagName)) {
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
customElements.define(tagName, class AbsoluteVoiceDeliveryRuntimeElement extends HTMLElement {
|
|
3196
|
+
mounted;
|
|
3197
|
+
connectedCallback() {
|
|
3198
|
+
const intervalMs = Number(this.getAttribute("interval-ms") ?? 5000);
|
|
3199
|
+
this.mounted = mountVoiceDeliveryRuntime(this, this.getAttribute("path") ?? "/api/voice-delivery-runtime", {
|
|
3200
|
+
description: this.getAttribute("description") ?? undefined,
|
|
3201
|
+
intervalMs: Number.isFinite(intervalMs) ? intervalMs : 5000,
|
|
3202
|
+
title: this.getAttribute("title") ?? undefined
|
|
3203
|
+
});
|
|
3204
|
+
}
|
|
3205
|
+
disconnectedCallback() {
|
|
3206
|
+
this.mounted?.close();
|
|
3207
|
+
this.mounted = undefined;
|
|
3208
|
+
}
|
|
3209
|
+
});
|
|
3210
|
+
};
|
|
3211
|
+
|
|
3212
|
+
// src/angular/voice-delivery-runtime.component.ts
|
|
3213
|
+
var _dec = [
|
|
3214
|
+
Component({
|
|
3215
|
+
selector: "absolute-voice-delivery-runtime",
|
|
3216
|
+
standalone: true,
|
|
3217
|
+
template: `
|
|
3218
|
+
<section
|
|
3219
|
+
class="absolute-voice-delivery-runtime"
|
|
3220
|
+
[class.absolute-voice-delivery-runtime--pass]="model().status === 'pass'"
|
|
3221
|
+
[class.absolute-voice-delivery-runtime--warn]="model().status === 'warn'"
|
|
3222
|
+
[class.absolute-voice-delivery-runtime--loading]="
|
|
3223
|
+
model().status === 'loading'
|
|
3224
|
+
"
|
|
3225
|
+
[class.absolute-voice-delivery-runtime--error]="
|
|
3226
|
+
model().status === 'error'
|
|
3227
|
+
"
|
|
3228
|
+
>
|
|
3229
|
+
<header class="absolute-voice-delivery-runtime__header">
|
|
3230
|
+
<span class="absolute-voice-delivery-runtime__eyebrow">{{
|
|
3231
|
+
model().title
|
|
3232
|
+
}}</span>
|
|
3233
|
+
<strong class="absolute-voice-delivery-runtime__label">{{
|
|
3234
|
+
model().label
|
|
3235
|
+
}}</strong>
|
|
3236
|
+
</header>
|
|
3237
|
+
<p class="absolute-voice-delivery-runtime__description">
|
|
3238
|
+
{{ model().description }}
|
|
3239
|
+
</p>
|
|
3240
|
+
<ul class="absolute-voice-delivery-runtime__surfaces">
|
|
3241
|
+
@for (surface of model().surfaces; track surface.id) {
|
|
3242
|
+
<li
|
|
3243
|
+
class="absolute-voice-delivery-runtime__surface"
|
|
3244
|
+
[class.absolute-voice-delivery-runtime__surface--pass]="
|
|
3245
|
+
surface.status === 'pass'
|
|
3246
|
+
"
|
|
3247
|
+
[class.absolute-voice-delivery-runtime__surface--warn]="
|
|
3248
|
+
surface.status === 'warn'
|
|
3249
|
+
"
|
|
3250
|
+
[class.absolute-voice-delivery-runtime__surface--disabled]="
|
|
3251
|
+
surface.status === 'disabled'
|
|
3252
|
+
"
|
|
3253
|
+
>
|
|
3254
|
+
<span>{{ surface.label }}</span>
|
|
3255
|
+
<strong>{{ surface.detail }}</strong>
|
|
3256
|
+
<small
|
|
3257
|
+
>{{ surface.failed }} failed /
|
|
3258
|
+
{{ surface.deadLettered }} dead-lettered</small
|
|
3259
|
+
>
|
|
3260
|
+
</li>
|
|
3261
|
+
}
|
|
3262
|
+
</ul>
|
|
3263
|
+
<div class="absolute-voice-delivery-runtime__actions">
|
|
3264
|
+
<button
|
|
3265
|
+
type="button"
|
|
3266
|
+
[disabled]="model().actionStatus === 'running'"
|
|
3267
|
+
(click)="tick()"
|
|
3268
|
+
>
|
|
3269
|
+
{{ model().actionStatus === "running" ? "Working..." : "Tick workers" }}
|
|
3270
|
+
</button>
|
|
3271
|
+
<button
|
|
3272
|
+
type="button"
|
|
3273
|
+
[disabled]="
|
|
3274
|
+
model().actionStatus === 'running' || deadLettered() === 0
|
|
3275
|
+
"
|
|
3276
|
+
(click)="requeueDeadLetters()"
|
|
3277
|
+
>
|
|
3278
|
+
Requeue dead letters
|
|
3279
|
+
</button>
|
|
3280
|
+
</div>
|
|
3281
|
+
@if (model().actionError) {
|
|
3282
|
+
<p class="absolute-voice-delivery-runtime__error">
|
|
3283
|
+
{{ model().actionError }}
|
|
3284
|
+
</p>
|
|
3285
|
+
}
|
|
3286
|
+
@if (model().error) {
|
|
3287
|
+
<p class="absolute-voice-delivery-runtime__error">
|
|
3288
|
+
{{ model().error }}
|
|
3289
|
+
</p>
|
|
3290
|
+
}
|
|
3291
|
+
</section>
|
|
3292
|
+
`
|
|
3293
|
+
})
|
|
3294
|
+
];
|
|
3295
|
+
var _dec2 = [
|
|
3296
|
+
Input()
|
|
3297
|
+
];
|
|
3298
|
+
var _dec3 = [
|
|
3299
|
+
Input()
|
|
3300
|
+
];
|
|
3301
|
+
var _dec4 = [
|
|
3302
|
+
Input()
|
|
3303
|
+
];
|
|
3304
|
+
var _dec5 = [
|
|
3305
|
+
Input()
|
|
3306
|
+
];
|
|
3307
|
+
var _init = __decoratorStart(undefined);
|
|
3308
|
+
|
|
3309
|
+
class VoiceDeliveryRuntimeComponent {
|
|
3310
|
+
constructor() {
|
|
3311
|
+
this.description = __runInitializers(_init, 8, this);
|
|
3312
|
+
__runInitializers(_init, 11, this);
|
|
3313
|
+
this.intervalMs = __runInitializers(_init, 12, this);
|
|
3314
|
+
__runInitializers(_init, 15, this);
|
|
3315
|
+
this.path = __runInitializers(_init, 16, this, "/api/voice-delivery-runtime");
|
|
3316
|
+
__runInitializers(_init, 19, this);
|
|
3317
|
+
this.title = __runInitializers(_init, 20, this);
|
|
3318
|
+
__runInitializers(_init, 23, this);
|
|
3319
|
+
}
|
|
3320
|
+
cleanup = () => {};
|
|
3321
|
+
store;
|
|
3322
|
+
model = signal14(createVoiceDeliveryRuntimeViewModel({
|
|
3323
|
+
actionError: null,
|
|
3324
|
+
actionStatus: "idle",
|
|
3325
|
+
error: null,
|
|
3326
|
+
isLoading: true
|
|
3327
|
+
}));
|
|
3328
|
+
ngOnInit() {
|
|
3329
|
+
const options = this.options();
|
|
3330
|
+
this.store = createVoiceDeliveryRuntimeStore(this.path, options);
|
|
3331
|
+
const sync = () => {
|
|
3332
|
+
this.model.set(createVoiceDeliveryRuntimeViewModel(this.store.getSnapshot(), options));
|
|
3333
|
+
};
|
|
3334
|
+
this.cleanup = this.store.subscribe(sync);
|
|
3335
|
+
sync();
|
|
3336
|
+
if (typeof window !== "undefined") {
|
|
3337
|
+
this.store.refresh().catch(() => {});
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
ngOnDestroy() {
|
|
3341
|
+
this.cleanup();
|
|
3342
|
+
this.store?.close();
|
|
3343
|
+
}
|
|
3344
|
+
deadLettered() {
|
|
3345
|
+
return this.model().surfaces.reduce((total, surface) => total + surface.deadLettered, 0);
|
|
3346
|
+
}
|
|
3347
|
+
tick() {
|
|
3348
|
+
this.store?.tick().catch(() => {});
|
|
3349
|
+
}
|
|
3350
|
+
requeueDeadLetters() {
|
|
3351
|
+
this.store?.requeueDeadLetters().catch(() => {});
|
|
3352
|
+
}
|
|
3353
|
+
options() {
|
|
3354
|
+
return {
|
|
3355
|
+
description: this.description,
|
|
3356
|
+
intervalMs: this.intervalMs,
|
|
3357
|
+
title: this.title
|
|
3358
|
+
};
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
__decorateElement(_init, 5, "description", _dec2, VoiceDeliveryRuntimeComponent);
|
|
3362
|
+
__decorateElement(_init, 5, "intervalMs", _dec3, VoiceDeliveryRuntimeComponent);
|
|
3363
|
+
__decorateElement(_init, 5, "path", _dec4, VoiceDeliveryRuntimeComponent);
|
|
3364
|
+
__decorateElement(_init, 5, "title", _dec5, VoiceDeliveryRuntimeComponent);
|
|
3365
|
+
VoiceDeliveryRuntimeComponent = __decorateElement(_init, 0, "VoiceDeliveryRuntimeComponent", _dec, VoiceDeliveryRuntimeComponent);
|
|
3366
|
+
__runInitializers(_init, 1, VoiceDeliveryRuntimeComponent);
|
|
3367
|
+
__decoratorMetadata(_init, VoiceDeliveryRuntimeComponent);
|
|
3368
|
+
let _VoiceDeliveryRuntimeComponent = VoiceDeliveryRuntimeComponent;
|
|
1414
3369
|
export {
|
|
3370
|
+
VoiceWorkflowStatusService,
|
|
3371
|
+
VoiceTurnQualityService,
|
|
3372
|
+
VoiceTurnLatencyService,
|
|
3373
|
+
VoiceTraceTimelineService,
|
|
1415
3374
|
VoiceStreamService,
|
|
3375
|
+
VoiceRoutingStatusService,
|
|
1416
3376
|
VoiceProviderStatusService,
|
|
1417
|
-
|
|
3377
|
+
VoiceProviderCapabilitiesService,
|
|
3378
|
+
VoiceOpsStatusService,
|
|
3379
|
+
VoiceOpsActionCenterService,
|
|
3380
|
+
VoiceDeliveryRuntimeService,
|
|
3381
|
+
VoiceDeliveryRuntimeComponent,
|
|
3382
|
+
VoiceControllerService,
|
|
3383
|
+
VoiceCampaignDialerProofService
|
|
1418
3384
|
};
|