@absolutejs/voice 0.0.22-beta.12 → 0.0.22-beta.120
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 +411 -3
- package/dist/agent.d.ts +2 -0
- package/dist/angular/index.d.ts +9 -0
- package/dist/angular/index.js +1278 -44
- package/dist/angular/voice-app-kit-status.service.d.ts +12 -0
- package/dist/angular/voice-campaign-dialer-proof.service.d.ts +14 -0
- package/dist/angular/voice-ops-status.component.d.ts +15 -0
- package/dist/angular/voice-provider-capabilities.service.d.ts +12 -0
- package/dist/angular/voice-provider-status.service.d.ts +12 -0
- package/dist/angular/voice-routing-status.service.d.ts +11 -0
- package/dist/angular/voice-stream.service.d.ts +2 -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/appKit.d.ts +100 -0
- package/dist/assistantHealth.d.ts +81 -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 +22 -0
- package/dist/client/appKitStatus.d.ts +19 -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/duplex.d.ts +1 -1
- package/dist/client/htmxBootstrap.js +587 -13
- package/dist/client/index.d.ts +40 -0
- package/dist/client/index.js +2028 -8
- package/dist/client/liveTurnLatency.d.ts +41 -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/providerStatus.d.ts +19 -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/diagnosticsRoutes.d.ts +44 -0
- package/dist/evalRoutes.d.ts +213 -0
- package/dist/fileStore.d.ts +3 -0
- package/dist/handoff.d.ts +54 -0
- package/dist/handoffHealth.d.ts +94 -0
- package/dist/index.d.ts +77 -8
- package/dist/index.js +12645 -3061
- package/dist/liveLatency.d.ts +78 -0
- package/dist/modelAdapters.d.ts +41 -2
- package/dist/openaiTTS.d.ts +18 -0
- package/dist/opsConsoleRoutes.d.ts +77 -0
- package/dist/opsWebhook.d.ts +126 -0
- package/dist/outcomeContract.d.ts +112 -0
- package/dist/phoneAgent.d.ts +58 -0
- package/dist/postgresStore.d.ts +5 -0
- package/dist/productionReadiness.d.ts +121 -0
- package/dist/providerAdapters.d.ts +48 -0
- package/dist/providerCapabilities.d.ts +92 -0
- package/dist/providerHealth.d.ts +79 -0
- package/dist/qualityRoutes.d.ts +76 -0
- package/dist/queue.d.ts +61 -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 +18 -0
- package/dist/react/index.js +2606 -12
- package/dist/react/useVoiceAppKitStatus.d.ts +8 -0
- package/dist/react/useVoiceCampaignDialerProof.d.ts +10 -0
- package/dist/react/useVoiceController.d.ts +2 -0
- package/dist/react/useVoiceProviderCapabilities.d.ts +8 -0
- package/dist/react/useVoiceProviderSimulationControls.d.ts +10 -0
- package/dist/react/useVoiceProviderStatus.d.ts +8 -0
- package/dist/react/useVoiceRoutingStatus.d.ts +8 -0
- package/dist/react/useVoiceStream.d.ts +2 -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/resilienceRoutes.d.ts +142 -0
- package/dist/sessionReplay.d.ts +175 -0
- package/dist/simulationSuite.d.ts +120 -0
- package/dist/sqliteStore.d.ts +5 -0
- package/dist/svelte/createVoiceAppKitStatus.d.ts +8 -0
- package/dist/svelte/createVoiceCampaignDialerProof.d.ts +9 -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 +10 -0
- 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 +1849 -4
- 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 +132 -0
- package/dist/telephonyOutcome.d.ts +201 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +2640 -21
- 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 +1 -1
- 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 +125 -2
- 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 +17 -0
- package/dist/vue/index.js +2520 -29
- package/dist/vue/useVoiceAppKitStatus.d.ts +9 -0
- package/dist/vue/useVoiceCampaignDialerProof.d.ts +11 -0
- package/dist/vue/useVoiceController.d.ts +1 -1
- package/dist/vue/useVoiceProviderCapabilities.d.ts +9 -0
- package/dist/vue/useVoiceProviderSimulationControls.d.ts +24 -0
- package/dist/vue/useVoiceProviderStatus.d.ts +9 -0
- package/dist/vue/useVoiceRoutingStatus.d.ts +8 -0
- package/dist/vue/useVoiceStream.d.ts +3 -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
|
@@ -188,6 +188,12 @@ var serverMessageToAction = (message) => {
|
|
|
188
188
|
sessionId: message.sessionId,
|
|
189
189
|
type: "complete"
|
|
190
190
|
};
|
|
191
|
+
case "call_lifecycle":
|
|
192
|
+
return {
|
|
193
|
+
event: message.event,
|
|
194
|
+
sessionId: message.sessionId,
|
|
195
|
+
type: "call_lifecycle"
|
|
196
|
+
};
|
|
191
197
|
case "error":
|
|
192
198
|
return {
|
|
193
199
|
message: normalizeErrorMessage(message.message),
|
|
@@ -231,7 +237,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
|
231
237
|
var noop = () => {};
|
|
232
238
|
var noopUnsubscribe = () => noop;
|
|
233
239
|
var NOOP_CONNECTION = {
|
|
234
|
-
|
|
240
|
+
callControl: noop,
|
|
235
241
|
close: noop,
|
|
236
242
|
endTurn: noop,
|
|
237
243
|
getReadyState: () => WS_CLOSED,
|
|
@@ -239,6 +245,7 @@ var NOOP_CONNECTION = {
|
|
|
239
245
|
getSessionId: () => "",
|
|
240
246
|
send: noop,
|
|
241
247
|
sendAudio: noop,
|
|
248
|
+
start: () => {},
|
|
242
249
|
subscribe: noopUnsubscribe
|
|
243
250
|
};
|
|
244
251
|
var createSessionId = () => crypto.randomUUID();
|
|
@@ -260,6 +267,7 @@ var isVoiceServerMessage = (value) => {
|
|
|
260
267
|
switch (value.type) {
|
|
261
268
|
case "audio":
|
|
262
269
|
case "assistant":
|
|
270
|
+
case "call_lifecycle":
|
|
263
271
|
case "complete":
|
|
264
272
|
case "error":
|
|
265
273
|
case "final":
|
|
@@ -400,6 +408,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
400
408
|
const endTurn = () => {
|
|
401
409
|
send({ type: "end_turn" });
|
|
402
410
|
};
|
|
411
|
+
const callControl = (message) => {
|
|
412
|
+
send({
|
|
413
|
+
...message,
|
|
414
|
+
type: "call_control"
|
|
415
|
+
});
|
|
416
|
+
};
|
|
403
417
|
const close = () => {
|
|
404
418
|
clearTimers();
|
|
405
419
|
if (state.ws) {
|
|
@@ -417,7 +431,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
417
431
|
};
|
|
418
432
|
connect();
|
|
419
433
|
return {
|
|
420
|
-
|
|
434
|
+
callControl,
|
|
421
435
|
close,
|
|
422
436
|
endTurn,
|
|
423
437
|
getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
|
|
@@ -425,6 +439,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
425
439
|
getSessionId: () => state.sessionId,
|
|
426
440
|
send,
|
|
427
441
|
sendAudio,
|
|
442
|
+
start,
|
|
428
443
|
subscribe
|
|
429
444
|
};
|
|
430
445
|
};
|
|
@@ -433,6 +448,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
433
448
|
var createInitialState = () => ({
|
|
434
449
|
assistantAudio: [],
|
|
435
450
|
assistantTexts: [],
|
|
451
|
+
call: null,
|
|
436
452
|
error: null,
|
|
437
453
|
isConnected: false,
|
|
438
454
|
scenarioId: null,
|
|
@@ -476,6 +492,20 @@ var createVoiceStreamStore = () => {
|
|
|
476
492
|
status: "completed"
|
|
477
493
|
};
|
|
478
494
|
break;
|
|
495
|
+
case "call_lifecycle":
|
|
496
|
+
state = {
|
|
497
|
+
...state,
|
|
498
|
+
call: {
|
|
499
|
+
...state.call,
|
|
500
|
+
disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
|
|
501
|
+
endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
|
|
502
|
+
events: [...state.call?.events ?? [], action.event],
|
|
503
|
+
lastEventAt: action.event.at,
|
|
504
|
+
startedAt: state.call?.startedAt ?? action.event.at
|
|
505
|
+
},
|
|
506
|
+
sessionId: action.sessionId
|
|
507
|
+
};
|
|
508
|
+
break;
|
|
479
509
|
case "connected":
|
|
480
510
|
state = {
|
|
481
511
|
...state,
|
|
@@ -562,6 +592,9 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
562
592
|
}
|
|
563
593
|
});
|
|
564
594
|
return {
|
|
595
|
+
callControl(message) {
|
|
596
|
+
connection.callControl(message);
|
|
597
|
+
},
|
|
565
598
|
close() {
|
|
566
599
|
unsubscribeConnection();
|
|
567
600
|
connection.close();
|
|
@@ -605,6 +638,9 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
605
638
|
get assistantAudio() {
|
|
606
639
|
return store.getSnapshot().assistantAudio;
|
|
607
640
|
},
|
|
641
|
+
get call() {
|
|
642
|
+
return store.getSnapshot().call;
|
|
643
|
+
},
|
|
608
644
|
sendAudio(audio) {
|
|
609
645
|
connection.sendAudio(audio);
|
|
610
646
|
},
|
|
@@ -900,6 +936,7 @@ var resolveVoiceRuntimePreset = (name = "default") => {
|
|
|
900
936
|
var createInitialState2 = (stream) => ({
|
|
901
937
|
assistantAudio: [...stream.assistantAudio],
|
|
902
938
|
assistantTexts: [...stream.assistantTexts],
|
|
939
|
+
call: stream.call,
|
|
903
940
|
error: stream.error,
|
|
904
941
|
isConnected: stream.isConnected,
|
|
905
942
|
isRecording: false,
|
|
@@ -929,6 +966,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
929
966
|
...state,
|
|
930
967
|
assistantAudio: [...stream.assistantAudio],
|
|
931
968
|
assistantTexts: [...stream.assistantTexts],
|
|
969
|
+
call: stream.call,
|
|
932
970
|
error: stream.error,
|
|
933
971
|
isConnected: stream.isConnected,
|
|
934
972
|
partial: stream.partial,
|
|
@@ -956,7 +994,13 @@ var createVoiceController = (path, options = {}) => {
|
|
|
956
994
|
capture = createMicrophoneCapture({
|
|
957
995
|
channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
|
|
958
996
|
onLevel: options.capture?.onLevel,
|
|
959
|
-
onAudio: (audio) =>
|
|
997
|
+
onAudio: (audio) => {
|
|
998
|
+
if (options.capture?.onAudio) {
|
|
999
|
+
options.capture.onAudio(audio, stream.sendAudio);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
stream.sendAudio(audio);
|
|
1003
|
+
},
|
|
960
1004
|
sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
|
|
961
1005
|
});
|
|
962
1006
|
return capture;
|
|
@@ -1006,6 +1050,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1006
1050
|
bindHTMX(bindingOptions) {
|
|
1007
1051
|
return bindVoiceHTMX(stream, bindingOptions);
|
|
1008
1052
|
},
|
|
1053
|
+
callControl: (message) => stream.callControl(message),
|
|
1009
1054
|
close,
|
|
1010
1055
|
endTurn: () => stream.endTurn(),
|
|
1011
1056
|
get error() {
|
|
@@ -1058,6 +1103,478 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1058
1103
|
},
|
|
1059
1104
|
get assistantAudio() {
|
|
1060
1105
|
return state.assistantAudio;
|
|
1106
|
+
},
|
|
1107
|
+
get call() {
|
|
1108
|
+
return state.call;
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
// src/client/audioPlayer.ts
|
|
1114
|
+
var DEFAULT_LOOKAHEAD_MS = 15;
|
|
1115
|
+
var createInitialState3 = () => ({
|
|
1116
|
+
activeSourceCount: 0,
|
|
1117
|
+
error: null,
|
|
1118
|
+
isActive: false,
|
|
1119
|
+
isPlaying: false,
|
|
1120
|
+
lastInterruptLatencyMs: undefined,
|
|
1121
|
+
lastPlaybackStopLatencyMs: undefined,
|
|
1122
|
+
processedChunkCount: 0,
|
|
1123
|
+
queuedChunkCount: 0
|
|
1124
|
+
});
|
|
1125
|
+
var getAudioContextCtor = () => {
|
|
1126
|
+
if (typeof window === "undefined") {
|
|
1127
|
+
return typeof AudioContext === "undefined" ? undefined : AudioContext;
|
|
1128
|
+
}
|
|
1129
|
+
return window.AudioContext ?? window.webkitAudioContext;
|
|
1130
|
+
};
|
|
1131
|
+
var decodePCM16LEChunk = (audioContext, chunk) => {
|
|
1132
|
+
const format = chunk.format;
|
|
1133
|
+
if (format.container !== "raw" || format.encoding !== "pcm_s16le") {
|
|
1134
|
+
throw new Error(`Unsupported assistant audio format: ${format.container}/${format.encoding}`);
|
|
1135
|
+
}
|
|
1136
|
+
const bytes = chunk.chunk;
|
|
1137
|
+
const channels = Math.max(1, format.channels);
|
|
1138
|
+
const sampleCount = Math.floor(bytes.byteLength / 2);
|
|
1139
|
+
const frameCount = Math.max(1, Math.floor(sampleCount / channels));
|
|
1140
|
+
const audioBuffer = audioContext.createBuffer(channels, frameCount, format.sampleRateHz);
|
|
1141
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1142
|
+
for (let channelIndex = 0;channelIndex < channels; channelIndex += 1) {
|
|
1143
|
+
const channelData = audioBuffer.getChannelData(channelIndex);
|
|
1144
|
+
for (let frameIndex = 0;frameIndex < frameCount; frameIndex += 1) {
|
|
1145
|
+
const sampleIndex = frameIndex * channels + channelIndex;
|
|
1146
|
+
const sampleOffset = sampleIndex * 2;
|
|
1147
|
+
if (sampleOffset + 1 >= bytes.byteLength) {
|
|
1148
|
+
channelData[frameIndex] = 0;
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
channelData[frameIndex] = view.getInt16(sampleOffset, true) / 32768;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return audioBuffer;
|
|
1155
|
+
};
|
|
1156
|
+
var createVoiceAudioPlayer = (source, options = {}) => {
|
|
1157
|
+
const subscribers = new Set;
|
|
1158
|
+
const sourceNodes = new Set;
|
|
1159
|
+
const lookaheadSeconds = (options.lookaheadMs ?? DEFAULT_LOOKAHEAD_MS) / 1000;
|
|
1160
|
+
let state = createInitialState3();
|
|
1161
|
+
let audioContext = null;
|
|
1162
|
+
let outputNode = null;
|
|
1163
|
+
let queueEndTime = 0;
|
|
1164
|
+
let syncPromise = Promise.resolve();
|
|
1165
|
+
let interruptStartedAt = null;
|
|
1166
|
+
let interruptPromise = null;
|
|
1167
|
+
let resolveInterruptPromise = null;
|
|
1168
|
+
let interruptFallbackTimer = null;
|
|
1169
|
+
const notify = () => {
|
|
1170
|
+
for (const subscriber of subscribers) {
|
|
1171
|
+
subscriber();
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
const setState = (next) => {
|
|
1175
|
+
state = {
|
|
1176
|
+
...state,
|
|
1177
|
+
...next
|
|
1178
|
+
};
|
|
1179
|
+
notify();
|
|
1180
|
+
};
|
|
1181
|
+
const clearError = () => {
|
|
1182
|
+
if (state.error !== null) {
|
|
1183
|
+
setState({ error: null });
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
const clearInterruptTimer = () => {
|
|
1187
|
+
if (interruptFallbackTimer !== null) {
|
|
1188
|
+
clearTimeout(interruptFallbackTimer);
|
|
1189
|
+
interruptFallbackTimer = null;
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
const resolveInterrupt = (latencyMs) => {
|
|
1193
|
+
clearInterruptTimer();
|
|
1194
|
+
interruptStartedAt = null;
|
|
1195
|
+
setState({
|
|
1196
|
+
activeSourceCount: sourceNodes.size,
|
|
1197
|
+
isPlaying: false,
|
|
1198
|
+
lastInterruptLatencyMs: latencyMs,
|
|
1199
|
+
lastPlaybackStopLatencyMs: state.lastPlaybackStopLatencyMs ?? latencyMs
|
|
1200
|
+
});
|
|
1201
|
+
resolveInterruptPromise?.();
|
|
1202
|
+
resolveInterruptPromise = null;
|
|
1203
|
+
interruptPromise = null;
|
|
1204
|
+
};
|
|
1205
|
+
const estimateOutputStopLatencyMs = (context) => {
|
|
1206
|
+
if (!context) {
|
|
1207
|
+
return 0;
|
|
1208
|
+
}
|
|
1209
|
+
return Math.max(0, ((context.baseLatency ?? 0) + (context.outputLatency ?? 0)) * 1000);
|
|
1210
|
+
};
|
|
1211
|
+
const restoreOutputGain = (context) => {
|
|
1212
|
+
if (!outputNode) {
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
const gainValue = 1;
|
|
1216
|
+
if (outputNode.gain.setValueAtTime) {
|
|
1217
|
+
outputNode.gain.setValueAtTime(gainValue, context?.currentTime ?? 0);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
outputNode.gain.value = gainValue;
|
|
1221
|
+
};
|
|
1222
|
+
const muteOutputGain = (context) => {
|
|
1223
|
+
if (!outputNode) {
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
const gainValue = 0;
|
|
1227
|
+
if (outputNode.gain.setValueAtTime) {
|
|
1228
|
+
outputNode.gain.setValueAtTime(gainValue, context?.currentTime ?? 0);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
outputNode.gain.value = gainValue;
|
|
1232
|
+
};
|
|
1233
|
+
const maybeResolveInterrupt = () => {
|
|
1234
|
+
if (interruptStartedAt === null || sourceNodes.size > 0) {
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
resolveInterrupt(Date.now() - interruptStartedAt);
|
|
1238
|
+
};
|
|
1239
|
+
const ensureAudioContext = async () => {
|
|
1240
|
+
if (audioContext) {
|
|
1241
|
+
return audioContext;
|
|
1242
|
+
}
|
|
1243
|
+
if (options.createAudioContext) {
|
|
1244
|
+
audioContext = options.createAudioContext();
|
|
1245
|
+
} else {
|
|
1246
|
+
const AudioContextCtor = getAudioContextCtor();
|
|
1247
|
+
if (!AudioContextCtor) {
|
|
1248
|
+
throw new Error("Assistant audio playback requires AudioContext support.");
|
|
1249
|
+
}
|
|
1250
|
+
audioContext = new AudioContextCtor;
|
|
1251
|
+
}
|
|
1252
|
+
if (audioContext.createGain) {
|
|
1253
|
+
outputNode = audioContext.createGain();
|
|
1254
|
+
outputNode.connect?.(audioContext.destination);
|
|
1255
|
+
}
|
|
1256
|
+
queueEndTime = audioContext.currentTime;
|
|
1257
|
+
return audioContext;
|
|
1258
|
+
};
|
|
1259
|
+
const scheduleChunk = async (chunk) => {
|
|
1260
|
+
const context = await ensureAudioContext();
|
|
1261
|
+
const buffer = decodePCM16LEChunk(context, chunk);
|
|
1262
|
+
const node = context.createBufferSource();
|
|
1263
|
+
node.buffer = buffer;
|
|
1264
|
+
node.connect(outputNode ?? context.destination);
|
|
1265
|
+
node.onended = () => {
|
|
1266
|
+
sourceNodes.delete(node);
|
|
1267
|
+
node.disconnect?.();
|
|
1268
|
+
setState({
|
|
1269
|
+
activeSourceCount: sourceNodes.size,
|
|
1270
|
+
isPlaying: sourceNodes.size > 0 && state.isActive
|
|
1271
|
+
});
|
|
1272
|
+
maybeResolveInterrupt();
|
|
1273
|
+
};
|
|
1274
|
+
const startAt = Math.max(context.currentTime + lookaheadSeconds, queueEndTime);
|
|
1275
|
+
queueEndTime = startAt + buffer.duration;
|
|
1276
|
+
sourceNodes.add(node);
|
|
1277
|
+
setState({
|
|
1278
|
+
activeSourceCount: sourceNodes.size,
|
|
1279
|
+
isPlaying: true
|
|
1280
|
+
});
|
|
1281
|
+
node.start(startAt);
|
|
1282
|
+
};
|
|
1283
|
+
const stopQueuedPlayback = (options2) => {
|
|
1284
|
+
for (const node of [...sourceNodes]) {
|
|
1285
|
+
node.stop?.();
|
|
1286
|
+
}
|
|
1287
|
+
queueEndTime = audioContext ? audioContext.currentTime : 0;
|
|
1288
|
+
if (options2?.forceClear) {
|
|
1289
|
+
for (const node of sourceNodes) {
|
|
1290
|
+
node.disconnect?.();
|
|
1291
|
+
}
|
|
1292
|
+
sourceNodes.clear();
|
|
1293
|
+
maybeResolveInterrupt();
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
const sync = async () => {
|
|
1297
|
+
if (!state.isActive) {
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
const nextChunks = source.assistantAudio.slice(state.processedChunkCount);
|
|
1301
|
+
if (nextChunks.length === 0) {
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
try {
|
|
1305
|
+
clearError();
|
|
1306
|
+
for (const chunk of nextChunks) {
|
|
1307
|
+
await scheduleChunk(chunk);
|
|
1308
|
+
}
|
|
1309
|
+
setState({
|
|
1310
|
+
processedChunkCount: source.assistantAudio.length,
|
|
1311
|
+
queuedChunkCount: state.queuedChunkCount + nextChunks.length
|
|
1312
|
+
});
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
setState({
|
|
1315
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
const queueSync = () => {
|
|
1320
|
+
syncPromise = syncPromise.then(() => sync(), () => sync());
|
|
1321
|
+
return syncPromise;
|
|
1322
|
+
};
|
|
1323
|
+
const unsubscribeSource = source.subscribe(() => {
|
|
1324
|
+
if (options.autoStart && !state.isActive && source.assistantAudio.length > 0) {
|
|
1325
|
+
player.start();
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
if (state.isActive) {
|
|
1329
|
+
queueSync();
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
const player = {
|
|
1333
|
+
close: async () => {
|
|
1334
|
+
unsubscribeSource();
|
|
1335
|
+
stopQueuedPlayback({ forceClear: true });
|
|
1336
|
+
clearInterruptTimer();
|
|
1337
|
+
resolveInterruptPromise?.();
|
|
1338
|
+
resolveInterruptPromise = null;
|
|
1339
|
+
interruptPromise = null;
|
|
1340
|
+
interruptStartedAt = null;
|
|
1341
|
+
if (audioContext && audioContext.state !== "closed") {
|
|
1342
|
+
await audioContext.close();
|
|
1343
|
+
}
|
|
1344
|
+
audioContext = null;
|
|
1345
|
+
outputNode?.disconnect?.();
|
|
1346
|
+
outputNode = null;
|
|
1347
|
+
queueEndTime = 0;
|
|
1348
|
+
setState({
|
|
1349
|
+
activeSourceCount: 0,
|
|
1350
|
+
isActive: false,
|
|
1351
|
+
isPlaying: false
|
|
1352
|
+
});
|
|
1353
|
+
},
|
|
1354
|
+
get activeSourceCount() {
|
|
1355
|
+
return state.activeSourceCount;
|
|
1356
|
+
},
|
|
1357
|
+
get error() {
|
|
1358
|
+
return state.error;
|
|
1359
|
+
},
|
|
1360
|
+
getSnapshot: () => state,
|
|
1361
|
+
get isActive() {
|
|
1362
|
+
return state.isActive;
|
|
1363
|
+
},
|
|
1364
|
+
get isPlaying() {
|
|
1365
|
+
return state.isPlaying;
|
|
1366
|
+
},
|
|
1367
|
+
interrupt: async () => {
|
|
1368
|
+
const startedAt = Date.now();
|
|
1369
|
+
const context = await ensureAudioContext();
|
|
1370
|
+
interruptStartedAt = startedAt;
|
|
1371
|
+
muteOutputGain(context);
|
|
1372
|
+
const playbackStopLatencyMs = Date.now() - startedAt + estimateOutputStopLatencyMs(context);
|
|
1373
|
+
setState({
|
|
1374
|
+
isActive: false,
|
|
1375
|
+
isPlaying: sourceNodes.size > 0,
|
|
1376
|
+
lastPlaybackStopLatencyMs: playbackStopLatencyMs
|
|
1377
|
+
});
|
|
1378
|
+
if (sourceNodes.size === 0) {
|
|
1379
|
+
resolveInterrupt(playbackStopLatencyMs);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
if (!interruptPromise) {
|
|
1383
|
+
interruptPromise = new Promise((resolve) => {
|
|
1384
|
+
resolveInterruptPromise = resolve;
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
clearInterruptTimer();
|
|
1388
|
+
interruptFallbackTimer = setTimeout(() => {
|
|
1389
|
+
for (const node of sourceNodes) {
|
|
1390
|
+
node.disconnect?.();
|
|
1391
|
+
}
|
|
1392
|
+
sourceNodes.clear();
|
|
1393
|
+
resolveInterrupt(Date.now() - startedAt);
|
|
1394
|
+
}, 250);
|
|
1395
|
+
stopQueuedPlayback();
|
|
1396
|
+
await interruptPromise;
|
|
1397
|
+
},
|
|
1398
|
+
get lastInterruptLatencyMs() {
|
|
1399
|
+
return state.lastInterruptLatencyMs;
|
|
1400
|
+
},
|
|
1401
|
+
get lastPlaybackStopLatencyMs() {
|
|
1402
|
+
return state.lastPlaybackStopLatencyMs;
|
|
1403
|
+
},
|
|
1404
|
+
pause: async () => {
|
|
1405
|
+
if (!audioContext) {
|
|
1406
|
+
setState({
|
|
1407
|
+
activeSourceCount: 0,
|
|
1408
|
+
isActive: false,
|
|
1409
|
+
isPlaying: false
|
|
1410
|
+
});
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
await audioContext.suspend();
|
|
1414
|
+
setState({
|
|
1415
|
+
activeSourceCount: sourceNodes.size,
|
|
1416
|
+
isActive: false,
|
|
1417
|
+
isPlaying: false
|
|
1418
|
+
});
|
|
1419
|
+
},
|
|
1420
|
+
get processedChunkCount() {
|
|
1421
|
+
return state.processedChunkCount;
|
|
1422
|
+
},
|
|
1423
|
+
get queuedChunkCount() {
|
|
1424
|
+
return state.queuedChunkCount;
|
|
1425
|
+
},
|
|
1426
|
+
start: async () => {
|
|
1427
|
+
try {
|
|
1428
|
+
clearError();
|
|
1429
|
+
const context = await ensureAudioContext();
|
|
1430
|
+
restoreOutputGain(context);
|
|
1431
|
+
if (context.state === "suspended") {
|
|
1432
|
+
await context.resume();
|
|
1433
|
+
}
|
|
1434
|
+
setState({
|
|
1435
|
+
activeSourceCount: sourceNodes.size,
|
|
1436
|
+
isActive: true,
|
|
1437
|
+
isPlaying: context.state === "running"
|
|
1438
|
+
});
|
|
1439
|
+
await queueSync();
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
setState({
|
|
1442
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1443
|
+
isActive: false,
|
|
1444
|
+
isPlaying: false
|
|
1445
|
+
});
|
|
1446
|
+
throw error;
|
|
1447
|
+
}
|
|
1448
|
+
},
|
|
1449
|
+
subscribe: (subscriber) => {
|
|
1450
|
+
subscribers.add(subscriber);
|
|
1451
|
+
return () => {
|
|
1452
|
+
subscribers.delete(subscriber);
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
return player;
|
|
1457
|
+
};
|
|
1458
|
+
|
|
1459
|
+
// src/client/bargeInMonitor.ts
|
|
1460
|
+
var DEFAULT_THRESHOLD_MS = 250;
|
|
1461
|
+
var createEventId = () => `barge-in:${Date.now()}:${crypto.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
1462
|
+
var summarize = (events, thresholdMs) => {
|
|
1463
|
+
const stopped = events.filter((event) => event.status === "stopped");
|
|
1464
|
+
const latencies = stopped.map((event) => event.latencyMs).filter((value) => typeof value === "number");
|
|
1465
|
+
const failed = stopped.filter((event) => typeof event.latencyMs === "number" && event.latencyMs > thresholdMs).length;
|
|
1466
|
+
const passed = stopped.length - failed;
|
|
1467
|
+
return {
|
|
1468
|
+
averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
|
|
1469
|
+
events: [...events],
|
|
1470
|
+
failed,
|
|
1471
|
+
lastEvent: events.at(-1),
|
|
1472
|
+
passed,
|
|
1473
|
+
status: events.length === 0 ? "empty" : failed > 0 ? "fail" : stopped.length === 0 ? "warn" : "pass",
|
|
1474
|
+
thresholdMs,
|
|
1475
|
+
total: stopped.length
|
|
1476
|
+
};
|
|
1477
|
+
};
|
|
1478
|
+
var createVoiceBargeInMonitor = (options = {}) => {
|
|
1479
|
+
const listeners = new Set;
|
|
1480
|
+
const thresholdMs = options.thresholdMs ?? DEFAULT_THRESHOLD_MS;
|
|
1481
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1482
|
+
const events = [];
|
|
1483
|
+
const emit = () => {
|
|
1484
|
+
for (const listener of listeners) {
|
|
1485
|
+
listener();
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
const postEvent = (event) => {
|
|
1489
|
+
if (!options.path || typeof fetchImpl !== "function") {
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
fetchImpl(options.path, {
|
|
1493
|
+
body: JSON.stringify(event),
|
|
1494
|
+
headers: {
|
|
1495
|
+
"Content-Type": "application/json"
|
|
1496
|
+
},
|
|
1497
|
+
method: "POST"
|
|
1498
|
+
}).catch(() => {});
|
|
1499
|
+
};
|
|
1500
|
+
const record = (status, input) => {
|
|
1501
|
+
const event = {
|
|
1502
|
+
at: Date.now(),
|
|
1503
|
+
id: createEventId(),
|
|
1504
|
+
latencyMs: input.latencyMs,
|
|
1505
|
+
playbackStopLatencyMs: input.playbackStopLatencyMs,
|
|
1506
|
+
reason: input.reason,
|
|
1507
|
+
sessionId: input.sessionId,
|
|
1508
|
+
status,
|
|
1509
|
+
thresholdMs
|
|
1510
|
+
};
|
|
1511
|
+
events.push(event);
|
|
1512
|
+
postEvent(event);
|
|
1513
|
+
emit();
|
|
1514
|
+
return event;
|
|
1515
|
+
};
|
|
1516
|
+
return {
|
|
1517
|
+
getSnapshot: () => summarize(events, thresholdMs),
|
|
1518
|
+
recordRequested: (input) => record("requested", input),
|
|
1519
|
+
recordSkipped: (input) => record("skipped", input),
|
|
1520
|
+
recordStopped: (input) => record("stopped", input),
|
|
1521
|
+
subscribe: (subscriber) => {
|
|
1522
|
+
listeners.add(subscriber);
|
|
1523
|
+
return () => {
|
|
1524
|
+
listeners.delete(subscriber);
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
// src/client/duplex.ts
|
|
1531
|
+
var DEFAULT_INTERRUPT_THRESHOLD = 0.08;
|
|
1532
|
+
var shouldInterruptForLevel = (level, options = {}) => (options.enabled ?? true) && level >= (options.interruptThreshold ?? DEFAULT_INTERRUPT_THRESHOLD);
|
|
1533
|
+
var bindVoiceBargeIn = (controller, player, options = {}) => {
|
|
1534
|
+
let lastPartial = controller.partial;
|
|
1535
|
+
const interruptIfPlaying = (reason) => {
|
|
1536
|
+
if (!player.isPlaying || options.enabled === false) {
|
|
1537
|
+
options.monitor?.recordSkipped({
|
|
1538
|
+
reason,
|
|
1539
|
+
sessionId: controller.sessionId
|
|
1540
|
+
});
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
options.monitor?.recordRequested({
|
|
1544
|
+
reason,
|
|
1545
|
+
sessionId: controller.sessionId
|
|
1546
|
+
});
|
|
1547
|
+
player.interrupt().then(() => {
|
|
1548
|
+
options.monitor?.recordStopped({
|
|
1549
|
+
latencyMs: player.lastInterruptLatencyMs,
|
|
1550
|
+
playbackStopLatencyMs: player.lastPlaybackStopLatencyMs,
|
|
1551
|
+
reason,
|
|
1552
|
+
sessionId: controller.sessionId
|
|
1553
|
+
});
|
|
1554
|
+
});
|
|
1555
|
+
};
|
|
1556
|
+
const unsubscribe = controller.subscribe(() => {
|
|
1557
|
+
if (options.interruptOnPartial === false) {
|
|
1558
|
+
lastPartial = controller.partial;
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
if (!lastPartial && controller.partial) {
|
|
1562
|
+
interruptIfPlaying("partial-transcript");
|
|
1563
|
+
}
|
|
1564
|
+
lastPartial = controller.partial;
|
|
1565
|
+
});
|
|
1566
|
+
return {
|
|
1567
|
+
close: () => {
|
|
1568
|
+
unsubscribe();
|
|
1569
|
+
},
|
|
1570
|
+
handleLevel: (level) => {
|
|
1571
|
+
if (shouldInterruptForLevel(level, options)) {
|
|
1572
|
+
interruptIfPlaying("input-level");
|
|
1573
|
+
}
|
|
1574
|
+
},
|
|
1575
|
+
sendAudio: (audio) => {
|
|
1576
|
+
interruptIfPlaying("manual-audio");
|
|
1577
|
+
controller.sendAudio(audio);
|
|
1061
1578
|
}
|
|
1062
1579
|
};
|
|
1063
1580
|
};
|
|
@@ -1174,6 +1691,13 @@ var parsePromptList = (value) => {
|
|
|
1174
1691
|
} catch {}
|
|
1175
1692
|
return DEFAULT_GUIDED_PROMPTS;
|
|
1176
1693
|
};
|
|
1694
|
+
var parseOptionalNumber = (value) => {
|
|
1695
|
+
if (!value) {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
const parsed = Number(value);
|
|
1699
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1700
|
+
};
|
|
1177
1701
|
var requireElement = (root, selector, ctor, name) => {
|
|
1178
1702
|
const value = selector ? document.querySelector(selector) : null;
|
|
1179
1703
|
if (value instanceof ctor) {
|
|
@@ -1224,6 +1748,13 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1224
1748
|
const guidedPrompts = parsePromptList(root.dataset.voiceGuidedPrompts);
|
|
1225
1749
|
const guidedLabel = root.dataset.voiceGuidedLabel ?? DEFAULT_GUIDED_LABEL;
|
|
1226
1750
|
const generalLabel = root.dataset.voiceGeneralLabel ?? DEFAULT_GENERAL_LABEL;
|
|
1751
|
+
const bargeInPath = root.dataset.voiceBargeInPath;
|
|
1752
|
+
const bargeInMonitor = bargeInPath ? createVoiceBargeInMonitor({
|
|
1753
|
+
path: bargeInPath,
|
|
1754
|
+
thresholdMs: parseOptionalNumber(root.dataset.voiceBargeInThresholdMs)
|
|
1755
|
+
}) : null;
|
|
1756
|
+
const bargeInRecentWindowMs = parseOptionalNumber(root.dataset.voiceBargeInRecentWindowMs) ?? 4000;
|
|
1757
|
+
const bargeInSpeechThreshold = parseOptionalNumber(root.dataset.voiceBargeInSpeechThreshold) ?? 0.04;
|
|
1227
1758
|
const syncElement = requireElement(document, root.dataset.voiceSync, HTMLElement, "voice-htmx-sync");
|
|
1228
1759
|
const connectionMetric = requireElement(root, root.dataset.voiceConnection, HTMLElement, "metric-connection");
|
|
1229
1760
|
const errorStatus = requireElement(root, root.dataset.voiceError, HTMLElement, "status-error");
|
|
@@ -1237,9 +1768,27 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1237
1768
|
const voiceMonitorCopy = requireElement(root, root.dataset.voiceMonitorCopy, HTMLElement, "voice-monitor-copy");
|
|
1238
1769
|
const voiceWaveGlow = requireElement(root, root.dataset.voiceWaveGlow, SVGPathElement, "voice-wave-glow");
|
|
1239
1770
|
const voiceWavePath = requireElement(root, root.dataset.voiceWavePath, SVGPathElement, "voice-wave-path");
|
|
1771
|
+
let activeMode = null;
|
|
1772
|
+
let hasStartedModes = {
|
|
1773
|
+
general: false,
|
|
1774
|
+
guided: false
|
|
1775
|
+
};
|
|
1776
|
+
let isCapturing = false;
|
|
1777
|
+
let micError = null;
|
|
1778
|
+
let waveLevels = createInitialVoiceWaveLevels();
|
|
1779
|
+
let guidedBargeInBinding = null;
|
|
1780
|
+
let generalBargeInBinding = null;
|
|
1240
1781
|
const guidedVoice = createVoiceController(guidedPath, {
|
|
1241
1782
|
capture: {
|
|
1783
|
+
onAudio: (audio, sendAudio) => {
|
|
1784
|
+
if (guidedBargeInBinding) {
|
|
1785
|
+
guidedBargeInBinding.sendAudio(audio);
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
sendAudio(audio);
|
|
1789
|
+
},
|
|
1242
1790
|
onLevel: (level) => {
|
|
1791
|
+
guidedBargeInBinding?.handleLevel(level);
|
|
1243
1792
|
waveLevels = pushVoiceWaveLevel(waveLevels, level);
|
|
1244
1793
|
renderWave();
|
|
1245
1794
|
}
|
|
@@ -1248,7 +1797,15 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1248
1797
|
});
|
|
1249
1798
|
const generalVoice = createVoiceController(generalPath, {
|
|
1250
1799
|
capture: {
|
|
1800
|
+
onAudio: (audio, sendAudio) => {
|
|
1801
|
+
if (generalBargeInBinding) {
|
|
1802
|
+
generalBargeInBinding.sendAudio(audio);
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
sendAudio(audio);
|
|
1806
|
+
},
|
|
1251
1807
|
onLevel: (level) => {
|
|
1808
|
+
generalBargeInBinding?.handleLevel(level);
|
|
1252
1809
|
waveLevels = pushVoiceWaveLevel(waveLevels, level);
|
|
1253
1810
|
renderWave();
|
|
1254
1811
|
}
|
|
@@ -1257,15 +1814,18 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1257
1814
|
});
|
|
1258
1815
|
const stopGuidedBinding = guidedVoice.bindHTMX({ element: syncElement });
|
|
1259
1816
|
const stopGeneralBinding = generalVoice.bindHTMX({ element: syncElement });
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1817
|
+
const guidedAudioPlayer = createVoiceAudioPlayer(guidedVoice);
|
|
1818
|
+
const generalAudioPlayer = createVoiceAudioPlayer(generalVoice);
|
|
1819
|
+
guidedBargeInBinding = bindVoiceBargeIn(guidedVoice, guidedAudioPlayer, {
|
|
1820
|
+
interruptThreshold: bargeInSpeechThreshold,
|
|
1821
|
+
monitor: bargeInMonitor ?? undefined
|
|
1822
|
+
});
|
|
1823
|
+
generalBargeInBinding = bindVoiceBargeIn(generalVoice, generalAudioPlayer, {
|
|
1824
|
+
interruptThreshold: bargeInSpeechThreshold,
|
|
1825
|
+
monitor: bargeInMonitor ?? undefined
|
|
1826
|
+
});
|
|
1268
1827
|
const currentVoice = () => activeMode === "general" ? generalVoice : guidedVoice;
|
|
1828
|
+
const currentAudioPlayer = () => activeMode === "general" ? generalAudioPlayer : guidedAudioPlayer;
|
|
1269
1829
|
const renderWave = () => {
|
|
1270
1830
|
const path = createVoiceWavePath(waveLevels);
|
|
1271
1831
|
voiceWaveGlow.setAttribute("d", path);
|
|
@@ -1343,8 +1903,18 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1343
1903
|
render();
|
|
1344
1904
|
}
|
|
1345
1905
|
};
|
|
1346
|
-
guidedVoice.subscribe(
|
|
1347
|
-
|
|
1906
|
+
guidedVoice.subscribe(() => {
|
|
1907
|
+
if (guidedVoice.assistantAudio.length > 0) {
|
|
1908
|
+
guidedAudioPlayer.start().catch(() => {});
|
|
1909
|
+
}
|
|
1910
|
+
render();
|
|
1911
|
+
});
|
|
1912
|
+
generalVoice.subscribe(() => {
|
|
1913
|
+
if (generalVoice.assistantAudio.length > 0) {
|
|
1914
|
+
generalAudioPlayer.start().catch(() => {});
|
|
1915
|
+
}
|
|
1916
|
+
render();
|
|
1917
|
+
});
|
|
1348
1918
|
startGuidedButton.addEventListener("click", () => {
|
|
1349
1919
|
startMode("guided");
|
|
1350
1920
|
});
|
|
@@ -1357,6 +1927,10 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1357
1927
|
window.addEventListener("beforeunload", () => {
|
|
1358
1928
|
guidedVoice.stopRecording();
|
|
1359
1929
|
generalVoice.stopRecording();
|
|
1930
|
+
guidedBargeInBinding?.close();
|
|
1931
|
+
generalBargeInBinding?.close();
|
|
1932
|
+
guidedAudioPlayer.close();
|
|
1933
|
+
generalAudioPlayer.close();
|
|
1360
1934
|
stopGuidedBinding();
|
|
1361
1935
|
stopGeneralBinding();
|
|
1362
1936
|
guidedVoice.close();
|