dsh-live-voice 0.7.0 → 0.7.2
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 +1 -1
- package/lib/asr/asr.worker.js +1 -1
- package/lib/asr/manifest.json +1 -1
- package/lib/client.js +295 -51
- package/lib/server.js +2 -2
- package/lib/vad/manifest.json +4 -4
- package/lib/vad/vad.capture.js +12 -0
- package/lib/vad/vad.worker.js +1 -1
- package/package.json +2 -2
- package/src/app/ARCHITECTURE.md +2 -0
- package/src/app/client/apply.tsx +39 -11
- package/src/modules/conversation/models/AttendeeClient.ts +3 -1
- package/src/modules/conversation/models/meeting.ts +15 -8
- package/src/modules/core/ARCHITECTURE.md +2 -0
- package/src/modules/core/coordinator.ts +199 -24
- package/src/modules/recognition/engines/whisper/WhisperRecognitionEngine.ts +12 -0
- package/src/modules/recognition/vad/SileroVadRuntime.ts +43 -3
- package/src/modules/recognition/vad/vad.capture.ts +12 -0
- package/src/modules/recording/RecordingRecognition.ts +59 -4
package/lib/client.js
CHANGED
|
@@ -152,7 +152,7 @@ var SessionSpeechMemory = class {
|
|
|
152
152
|
};
|
|
153
153
|
|
|
154
154
|
// src/modules/recognition/vad/assets.ts
|
|
155
|
-
var sileroVadAssetBase = () => "/api/dsh-live-voice/vad/" + encodeURIComponent(true ? "0.7.
|
|
155
|
+
var sileroVadAssetBase = () => "/api/dsh-live-voice/vad/" + encodeURIComponent(true ? "0.7.2" : "test") + "/";
|
|
156
156
|
async function sileroVadAvailable(fetchImpl = globalThis.fetch) {
|
|
157
157
|
try {
|
|
158
158
|
const response = await fetchImpl(sileroVadAssetBase() + "availability", {
|
|
@@ -458,7 +458,7 @@ async function attachSileroVadCapture(context, source, stream, onPcm) {
|
|
|
458
458
|
let loading = captureLoads.get(context);
|
|
459
459
|
if (!loading) {
|
|
460
460
|
loading = context.audioWorklet.addModule(
|
|
461
|
-
sileroVadAssetBase() + "vad.capture.js?revision=
|
|
461
|
+
sileroVadAssetBase() + "vad.capture.js?revision=pause-v2"
|
|
462
462
|
);
|
|
463
463
|
captureLoads.set(context, loading);
|
|
464
464
|
void loading.catch(() => {
|
|
@@ -477,6 +477,10 @@ async function attachSileroVadCapture(context, source, stream, onPcm) {
|
|
|
477
477
|
let released = false;
|
|
478
478
|
let acknowledged = false;
|
|
479
479
|
let finishing;
|
|
480
|
+
let flushing;
|
|
481
|
+
let resolveFlush;
|
|
482
|
+
let rejectFlush;
|
|
483
|
+
let flushTimer;
|
|
480
484
|
let resolveFinish;
|
|
481
485
|
let rejectFinish;
|
|
482
486
|
let failure2;
|
|
@@ -487,6 +491,8 @@ async function attachSileroVadCapture(context, source, stream, onPcm) {
|
|
|
487
491
|
if (released) return;
|
|
488
492
|
released = true;
|
|
489
493
|
clearTimeout(timer);
|
|
494
|
+
clearTimeout(flushTimer);
|
|
495
|
+
rejectFlush?.(cancelled());
|
|
490
496
|
unsubscribe();
|
|
491
497
|
node.port.onmessage = null;
|
|
492
498
|
node.onprocessorerror = null;
|
|
@@ -504,6 +510,8 @@ async function attachSileroVadCapture(context, source, stream, onPcm) {
|
|
|
504
510
|
if (released) return;
|
|
505
511
|
failure2 = error;
|
|
506
512
|
rejectFinish?.(error);
|
|
513
|
+
rejectFlush?.(error);
|
|
514
|
+
rejectFlush = void 0;
|
|
507
515
|
cleanup();
|
|
508
516
|
};
|
|
509
517
|
unsubscribe = stream.onInvalidated((error) => {
|
|
@@ -515,7 +523,15 @@ async function attachSileroVadCapture(context, source, stream, onPcm) {
|
|
|
515
523
|
}
|
|
516
524
|
node.port.onmessage = (event) => {
|
|
517
525
|
if (released || stream.released) return;
|
|
518
|
-
if (event.data?.type === "
|
|
526
|
+
if (event.data?.type === "flushed") {
|
|
527
|
+
clearTimeout(flushTimer);
|
|
528
|
+
void stream.flush().then(() => {
|
|
529
|
+
const done = resolveFlush;
|
|
530
|
+
resolveFlush = rejectFlush = void 0;
|
|
531
|
+
flushing = void 0;
|
|
532
|
+
done?.();
|
|
533
|
+
}, fail);
|
|
534
|
+
} else if (event.data?.type === "stopped") {
|
|
519
535
|
if (!finishing || acknowledged) return;
|
|
520
536
|
acknowledged = true;
|
|
521
537
|
clearTimeout(timer);
|
|
@@ -556,6 +572,25 @@ async function attachSileroVadCapture(context, source, stream, onPcm) {
|
|
|
556
572
|
throw error;
|
|
557
573
|
}
|
|
558
574
|
return {
|
|
575
|
+
resume() {
|
|
576
|
+
if (released || finishing) throw failure2 || cancelled();
|
|
577
|
+
node.port.postMessage({ type: "resume" });
|
|
578
|
+
},
|
|
579
|
+
flush(pause = false) {
|
|
580
|
+
if (flushing) return flushing;
|
|
581
|
+
if (released || stream.released || finishing) return Promise.reject(failure2 || cancelled());
|
|
582
|
+
flushing = new Promise((resolve, reject) => {
|
|
583
|
+
resolveFlush = resolve;
|
|
584
|
+
rejectFlush = reject;
|
|
585
|
+
});
|
|
586
|
+
flushTimer = setTimeout(() => fail(new Error("VAD capture flush timed out.")), 3e3);
|
|
587
|
+
try {
|
|
588
|
+
node.port.postMessage({ type: "flush", pause });
|
|
589
|
+
} catch (error) {
|
|
590
|
+
fail(error instanceof Error ? error : new Error("VAD capture flush failed."));
|
|
591
|
+
}
|
|
592
|
+
return flushing;
|
|
593
|
+
},
|
|
559
594
|
finish() {
|
|
560
595
|
if (finishing) return finishing;
|
|
561
596
|
if (released || stream.released) return finishing = Promise.reject(failure2 || cancelled());
|
|
@@ -2388,6 +2423,7 @@ async function startRecordingRecognition(engine, options, capture) {
|
|
|
2388
2423
|
await writer.finish("interrupted");
|
|
2389
2424
|
throw Object.assign(new Error("Cancelled"), { name: "AbortError" });
|
|
2390
2425
|
}
|
|
2426
|
+
let suspension = null;
|
|
2391
2427
|
const session = {
|
|
2392
2428
|
writer,
|
|
2393
2429
|
detector: capture.silero ? "silero" : "energy",
|
|
@@ -2408,6 +2444,31 @@ async function startRecordingRecognition(engine, options, capture) {
|
|
|
2408
2444
|
lang,
|
|
2409
2445
|
operation: uid(),
|
|
2410
2446
|
capturedSamples: 0,
|
|
2447
|
+
captureSuspended: false,
|
|
2448
|
+
suspendCapture: () => {
|
|
2449
|
+
if (session.captureSuspended) return Promise.resolve(true);
|
|
2450
|
+
if (suspension) return suspension;
|
|
2451
|
+
if (session.finished || !valid()) return Promise.resolve(false);
|
|
2452
|
+
suspension = (async () => {
|
|
2453
|
+
await engine.meter.context.suspend();
|
|
2454
|
+
if (!valid()) return false;
|
|
2455
|
+
session.captureSuspended = true;
|
|
2456
|
+
return true;
|
|
2457
|
+
})().finally(() => {
|
|
2458
|
+
suspension = null;
|
|
2459
|
+
});
|
|
2460
|
+
return suspension;
|
|
2461
|
+
},
|
|
2462
|
+
resumeCapture: async () => {
|
|
2463
|
+
if (!session.captureSuspended) return true;
|
|
2464
|
+
if (session.finished || !valid()) return false;
|
|
2465
|
+
session.capture?.resume?.();
|
|
2466
|
+
await engine.meter.context.resume();
|
|
2467
|
+
if (!valid()) return false;
|
|
2468
|
+
session.captureSuspended = false;
|
|
2469
|
+
return true;
|
|
2470
|
+
},
|
|
2471
|
+
pauseForNavigation: async () => false,
|
|
2411
2472
|
finishRecording: async () => {
|
|
2412
2473
|
},
|
|
2413
2474
|
stopRecording: async () => {
|
|
@@ -2502,10 +2563,10 @@ async function startRecordingRecognition(engine, options, capture) {
|
|
|
2502
2563
|
queuedThrough = Math.max(queuedThrough, end);
|
|
2503
2564
|
await releaseCapturedRanges();
|
|
2504
2565
|
};
|
|
2505
|
-
async function releaseCapturedRanges() {
|
|
2566
|
+
async function releaseCapturedRanges(clampToCaptured = false) {
|
|
2506
2567
|
if (!valid()) return;
|
|
2507
2568
|
const captured = writer.session.capturedSamples;
|
|
2508
|
-
while (awaitingTail.length && (session.finished || awaitingTail[0].endSample <= captured)) {
|
|
2569
|
+
while (awaitingTail.length && (session.finished || clampToCaptured || awaitingTail[0].endSample <= captured)) {
|
|
2509
2570
|
const pending = awaitingTail[0];
|
|
2510
2571
|
const attempt = { ...pending, endSample: Math.min(pending.endSample, captured) };
|
|
2511
2572
|
await writer.attempt(attempt);
|
|
@@ -2560,6 +2621,15 @@ async function startRecordingRecognition(engine, options, capture) {
|
|
|
2560
2621
|
}
|
|
2561
2622
|
});
|
|
2562
2623
|
session.pinDetector = detector;
|
|
2624
|
+
session.pauseForNavigation = async () => {
|
|
2625
|
+
if (suspension) await suspension;
|
|
2626
|
+
if (!session.captureSuspended) await session.capture?.flush?.(true);
|
|
2627
|
+
if (!await session.suspendCapture() || !valid()) return false;
|
|
2628
|
+
detector.finish();
|
|
2629
|
+
await session.tail;
|
|
2630
|
+
await serialize(() => releaseCapturedRanges(true), false, true);
|
|
2631
|
+
return valid();
|
|
2632
|
+
};
|
|
2563
2633
|
async function drain() {
|
|
2564
2634
|
if (session.draining || !valid()) return;
|
|
2565
2635
|
session.draining = true;
|
|
@@ -2829,7 +2899,7 @@ async function startRecordingRecognition(engine, options, capture) {
|
|
|
2829
2899
|
awaitingTail.length = 0;
|
|
2830
2900
|
await recovering;
|
|
2831
2901
|
session.activeRequest?.abort();
|
|
2832
|
-
if (session.finishing) session.capture?.release();
|
|
2902
|
+
if (session.finishing || session.captureSuspended) session.capture?.release();
|
|
2833
2903
|
else {
|
|
2834
2904
|
try {
|
|
2835
2905
|
await session.capture?.finish();
|
|
@@ -3065,6 +3135,18 @@ var WhisperHttpRecognitionEngine = class {
|
|
|
3065
3135
|
finish() {
|
|
3066
3136
|
return this.session?.finishRecording() ?? Promise.resolve();
|
|
3067
3137
|
}
|
|
3138
|
+
get captureSuspended() {
|
|
3139
|
+
return this.session?.captureSuspended === true;
|
|
3140
|
+
}
|
|
3141
|
+
suspendCapture() {
|
|
3142
|
+
return this.session?.suspendCapture() ?? Promise.resolve(false);
|
|
3143
|
+
}
|
|
3144
|
+
resumeCapture() {
|
|
3145
|
+
return this.session?.resumeCapture() ?? Promise.resolve(false);
|
|
3146
|
+
}
|
|
3147
|
+
pauseForNavigation() {
|
|
3148
|
+
return this.session?.pauseForNavigation() ?? Promise.resolve(false);
|
|
3149
|
+
}
|
|
3068
3150
|
async stop() {
|
|
3069
3151
|
this.startGeneration = (this.startGeneration || 0) + 1;
|
|
3070
3152
|
const session = this.session;
|
|
@@ -3425,7 +3507,7 @@ async function removeBrowserModel(key, storage = caches) {
|
|
|
3425
3507
|
}
|
|
3426
3508
|
|
|
3427
3509
|
// src/modules/recognition/local/assets.ts
|
|
3428
|
-
var browserAsrAssetBase = () => "/api/dsh-live-voice/asr/" + encodeURIComponent(true ? "0.7.
|
|
3510
|
+
var browserAsrAssetBase = () => "/api/dsh-live-voice/asr/" + encodeURIComponent(true ? "0.7.2" : "test") + "/";
|
|
3429
3511
|
|
|
3430
3512
|
// src/modules/recognition/local/runtimeIdentity.ts
|
|
3431
3513
|
var workerProtocol = "asr-worker-v3-ort-1.31.0-dev.20260914-8d85527a0";
|
|
@@ -10258,6 +10340,9 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
10258
10340
|
this.unfinished = /* @__PURE__ */ new Set();
|
|
10259
10341
|
this.suppressed = /* @__PURE__ */ new Set();
|
|
10260
10342
|
this.inputTail = Promise.resolve();
|
|
10343
|
+
this.navigationPaused = false;
|
|
10344
|
+
this.navigationEpoch = 0;
|
|
10345
|
+
this.navigationResults = [];
|
|
10261
10346
|
this.speechBarrier = Promise.resolve();
|
|
10262
10347
|
this.disposed = false;
|
|
10263
10348
|
this.inputReleaseError = null;
|
|
@@ -10502,6 +10587,10 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
10502
10587
|
});
|
|
10503
10588
|
}
|
|
10504
10589
|
_recoverInput(error) {
|
|
10590
|
+
if (this.navigationPaused) {
|
|
10591
|
+
this.patch({ inputError: message(error), error: message(error) });
|
|
10592
|
+
return;
|
|
10593
|
+
}
|
|
10505
10594
|
const attempt = this.inputRecoveryAttempt || 0;
|
|
10506
10595
|
const retry = !this.inputReleaseError && isRetryableVoiceError(error) && attempt < MAX_RECOVERY_ATTEMPTS;
|
|
10507
10596
|
const generation = (this.inputRecoveryGeneration || 0) + 1;
|
|
@@ -10566,6 +10655,23 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
10566
10655
|
});
|
|
10567
10656
|
return result;
|
|
10568
10657
|
}
|
|
10658
|
+
async _suspendInputCapture() {
|
|
10659
|
+
return this._input(async () => {
|
|
10660
|
+
if (!this.snapshot.listening || typeof this.recognition.suspendCapture !== "function")
|
|
10661
|
+
return false;
|
|
10662
|
+
return await this.recognition.suspendCapture() !== false;
|
|
10663
|
+
});
|
|
10664
|
+
}
|
|
10665
|
+
async _resumeInputCapture(valid = () => true) {
|
|
10666
|
+
return this._input(async () => {
|
|
10667
|
+
if (!valid() || !this.snapshot.listening || typeof this.recognition.resumeCapture !== "function")
|
|
10668
|
+
return false;
|
|
10669
|
+
return await this.recognition.resumeCapture() !== false;
|
|
10670
|
+
});
|
|
10671
|
+
}
|
|
10672
|
+
_hasSuspendedInputCapture() {
|
|
10673
|
+
return this.snapshot.listening && this.recognition.captureSuspended === true;
|
|
10674
|
+
}
|
|
10569
10675
|
async _releaseInput() {
|
|
10570
10676
|
const recognitionResult = await Promise.resolve().then(() => this.recognition.stop()).then(
|
|
10571
10677
|
() => ({ status: "fulfilled" }),
|
|
@@ -10874,8 +10980,19 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
10874
10980
|
void this.resumeSpeech();
|
|
10875
10981
|
}, this.snapshot.settings.assistantSpeechDelaySeconds * 1e3);
|
|
10876
10982
|
}
|
|
10877
|
-
onResult(
|
|
10983
|
+
onResult(result) {
|
|
10984
|
+
const {
|
|
10985
|
+
final = "",
|
|
10986
|
+
interim = "",
|
|
10987
|
+
startedAt,
|
|
10988
|
+
paragraphBefore = false,
|
|
10989
|
+
onCommandRecognized
|
|
10990
|
+
} = result;
|
|
10878
10991
|
if (this.disposed || !this.snapshot.listening && !this.snapshot.starting) return;
|
|
10992
|
+
if (this.navigationPaused) {
|
|
10993
|
+
if (final) this.navigationResults.push(result);
|
|
10994
|
+
return;
|
|
10995
|
+
}
|
|
10879
10996
|
const recognizing = this.recognition.activityManaged ? this.snapshot.recognizing : !!interim;
|
|
10880
10997
|
if (this.snapshot.speaking && !this.snapshot.paused && this.snapshot.settings.mode === "speaker")
|
|
10881
10998
|
return;
|
|
@@ -10960,7 +11077,7 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
10960
11077
|
return this.snapshot.pendingDeliveryTranscriptions ?? this.snapshot.pendingTranscriptions;
|
|
10961
11078
|
}
|
|
10962
11079
|
maybeScheduleAutoSend(draft = this.autoSendIntent) {
|
|
10963
|
-
if (draft == null || draft !== this.composer.getDraft()) return;
|
|
11080
|
+
if (this.navigationPaused || draft == null || draft !== this.composer.getDraft()) return;
|
|
10964
11081
|
if (this.autoSendTimer !== null) return;
|
|
10965
11082
|
if (this.snapshot.settings.sendingMode === "manual" || this.snapshot.muted || this.composer.canAutoSend?.() === false || this.snapshot.recognizing || this.deliveryPending() > 0)
|
|
10966
11083
|
return;
|
|
@@ -11015,6 +11132,9 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11015
11132
|
if (this.autoSendIntent != null && draft !== this.autoSendIntent) this.cancelAutoSend();
|
|
11016
11133
|
}
|
|
11017
11134
|
stopListening() {
|
|
11135
|
+
++this.navigationEpoch;
|
|
11136
|
+
this.navigationPaused = false;
|
|
11137
|
+
this.navigationResults = [];
|
|
11018
11138
|
this._cancelRecovery("input");
|
|
11019
11139
|
this.holdToTalkRelease = false;
|
|
11020
11140
|
this.cancelAutoSend();
|
|
@@ -11160,11 +11280,64 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11160
11280
|
this.patch({ paused: checkpoint.paused && this.queue.length > 0 });
|
|
11161
11281
|
this._syncSpeechSegments();
|
|
11162
11282
|
}
|
|
11163
|
-
async
|
|
11283
|
+
async pauseInputForNavigation() {
|
|
11284
|
+
if (!this.snapshot.listening || typeof this.recognition.pauseForNavigation !== "function")
|
|
11285
|
+
return false;
|
|
11286
|
+
const generation = ++this.navigationEpoch;
|
|
11287
|
+
this.navigationPaused = true;
|
|
11288
|
+
this.cancelAutoSend();
|
|
11289
|
+
try {
|
|
11290
|
+
const paused = await this._input(() => this.recognition.pauseForNavigation());
|
|
11291
|
+
if (!paused) throw new Error("Microphone capture could not be paused safely.");
|
|
11292
|
+
return true;
|
|
11293
|
+
} catch (error) {
|
|
11294
|
+
if (generation !== this.navigationEpoch || this.disposed) return false;
|
|
11295
|
+
try {
|
|
11296
|
+
await this._input(() => this._releaseInput());
|
|
11297
|
+
this.inputReleaseError = null;
|
|
11298
|
+
} catch (cleanup) {
|
|
11299
|
+
this.inputReleaseError = cleanup;
|
|
11300
|
+
this.patch({ inputError: message(cleanup), error: message(cleanup) });
|
|
11301
|
+
throw cleanup;
|
|
11302
|
+
}
|
|
11303
|
+
if (generation !== this.navigationEpoch || this.disposed) return false;
|
|
11304
|
+
this.patch({
|
|
11305
|
+
listening: false,
|
|
11306
|
+
starting: false,
|
|
11307
|
+
recognizing: false,
|
|
11308
|
+
inputError: message(error),
|
|
11309
|
+
error: message(error)
|
|
11310
|
+
});
|
|
11311
|
+
return false;
|
|
11312
|
+
}
|
|
11313
|
+
}
|
|
11314
|
+
async resumeInputFromNavigation() {
|
|
11315
|
+
if (!this.navigationPaused || this.disposed) return false;
|
|
11316
|
+
const generation = this.navigationEpoch;
|
|
11317
|
+
if (!this.snapshot.listening) {
|
|
11318
|
+
const queued = this.navigationResults.splice(0);
|
|
11319
|
+
this.navigationPaused = false;
|
|
11320
|
+
await this._startInput(this.snapshot.conversation);
|
|
11321
|
+
if (this.disposed || generation !== this.navigationEpoch) return false;
|
|
11322
|
+
if (!this.snapshot.listening) {
|
|
11323
|
+
this.navigationPaused = true;
|
|
11324
|
+
this.navigationResults.unshift(...queued);
|
|
11325
|
+
return false;
|
|
11326
|
+
}
|
|
11327
|
+
for (const result of queued) this.onResult(result);
|
|
11328
|
+
return true;
|
|
11329
|
+
}
|
|
11330
|
+
const resumed = await this._input(() => this.recognition.resumeCapture());
|
|
11331
|
+
if (!resumed || this.disposed || generation !== this.navigationEpoch) return false;
|
|
11332
|
+
this.navigationPaused = false;
|
|
11333
|
+
for (const result of this.navigationResults.splice(0)) this.onResult(result);
|
|
11334
|
+
return true;
|
|
11335
|
+
}
|
|
11336
|
+
async suspendSpeech(preserveInput = false) {
|
|
11164
11337
|
this.speechSuspended = true;
|
|
11165
11338
|
await Promise.all([
|
|
11166
11339
|
_VoiceCoordinator.prototype.stopSpeech.call(this, false),
|
|
11167
|
-
this.stopListening()
|
|
11340
|
+
preserveInput ? this.pauseInputForNavigation() : this.stopListening()
|
|
11168
11341
|
]);
|
|
11169
11342
|
}
|
|
11170
11343
|
discardSpeechMessage(id) {
|
|
@@ -11232,6 +11405,13 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11232
11405
|
if (epoch === this.speechEpoch) this.patch({ error: message(error) });
|
|
11233
11406
|
return;
|
|
11234
11407
|
}
|
|
11408
|
+
if (epoch === this.speechEpoch && !this.disposed && resumeListening && !this.navigationPaused && this.snapshot.settings.mode === "speaker" && this._hasSuspendedInputCapture()) {
|
|
11409
|
+
try {
|
|
11410
|
+
await this._resumeInputCapture();
|
|
11411
|
+
} catch (error) {
|
|
11412
|
+
this.patch({ inputError: message(error), error: message(error) });
|
|
11413
|
+
}
|
|
11414
|
+
}
|
|
11235
11415
|
if (epoch === this.speechEpoch && !this.disposed && resumeListening && !this.snapshot.inputRecovery && this.snapshot.conversation && !this.snapshot.listening && !this.snapshot.starting)
|
|
11236
11416
|
await this._startInput(true);
|
|
11237
11417
|
}
|
|
@@ -11278,22 +11458,53 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11278
11458
|
getSpeechProgress() {
|
|
11279
11459
|
return this.engines[this.snapshot.speechEngine]?.getPlaybackProgress?.() ?? null;
|
|
11280
11460
|
}
|
|
11461
|
+
async _restorePausedSpeechInput(epoch, control) {
|
|
11462
|
+
const valid = () => !this.disposed && !this.navigationPaused && epoch === this.speechEpoch && control === this.controlEpoch && this.snapshot.paused && this.snapshot.settings.mode === "speaker";
|
|
11463
|
+
if (!valid()) return;
|
|
11464
|
+
try {
|
|
11465
|
+
if (this._hasSuspendedInputCapture()) {
|
|
11466
|
+
await this._resumeInputCapture(valid);
|
|
11467
|
+
} else if (this.snapshot.conversation && !this.snapshot.listening && !this.snapshot.starting && !this.snapshot.inputRecovery) {
|
|
11468
|
+
await this._startInput(true);
|
|
11469
|
+
}
|
|
11470
|
+
} catch (error) {
|
|
11471
|
+
if (valid()) this.patch({ inputError: message(error), error: message(error) });
|
|
11472
|
+
}
|
|
11473
|
+
}
|
|
11474
|
+
async _gateResumedSpeechInput() {
|
|
11475
|
+
if (this.snapshot.settings.mode !== "speaker") return;
|
|
11476
|
+
const suspended = await this._suspendInputCapture();
|
|
11477
|
+
if (!suspended) {
|
|
11478
|
+
await this.stopListening();
|
|
11479
|
+
if (this.inputReleaseError) throw this.inputReleaseError;
|
|
11480
|
+
}
|
|
11481
|
+
}
|
|
11281
11482
|
async pauseSpeech(automatic = false) {
|
|
11282
11483
|
if (this.snapshot.speechRecovery?.retrying) this._cancelRecovery("speech", true);
|
|
11283
11484
|
if (!automatic) {
|
|
11284
11485
|
this._cancelInterruptionResume();
|
|
11285
11486
|
this.interruptionPausedSpeech = false;
|
|
11286
11487
|
}
|
|
11287
|
-
if (this.disposed
|
|
11488
|
+
if (this.disposed) return;
|
|
11489
|
+
if (this.snapshot.paused) {
|
|
11490
|
+
const epoch2 = this.speechEpoch;
|
|
11491
|
+
const control2 = ++this.controlEpoch;
|
|
11492
|
+
await this.inputTail;
|
|
11493
|
+
await this._restorePausedSpeechInput(epoch2, control2);
|
|
11494
|
+
return;
|
|
11495
|
+
}
|
|
11288
11496
|
if (!this.snapshot.speaking && this.snapshot.speechRunActive) {
|
|
11289
11497
|
this._cancelAssistantSpeechTimer();
|
|
11498
|
+
const control2 = ++this.controlEpoch;
|
|
11290
11499
|
this.patch({ paused: true });
|
|
11500
|
+
await this._restorePausedSpeechInput(this.speechEpoch, control2);
|
|
11291
11501
|
return;
|
|
11292
11502
|
}
|
|
11293
11503
|
if (!this.snapshot.speaking) return;
|
|
11294
11504
|
if (this.speechLaunchPending) {
|
|
11295
|
-
++this.controlEpoch;
|
|
11505
|
+
const control2 = ++this.controlEpoch;
|
|
11296
11506
|
this.patch({ paused: true });
|
|
11507
|
+
await this._restorePausedSpeechInput(this.speechEpoch, control2);
|
|
11297
11508
|
return;
|
|
11298
11509
|
}
|
|
11299
11510
|
const epoch = this.speechEpoch;
|
|
@@ -11304,6 +11515,7 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11304
11515
|
if (epoch === this.speechEpoch && control === this.controlEpoch && this.snapshot.speaking && result !== false) {
|
|
11305
11516
|
this.patch({ paused: true });
|
|
11306
11517
|
this.activeRecordingTimeline?.("assistant-pause");
|
|
11518
|
+
await this._restorePausedSpeechInput(epoch, control);
|
|
11307
11519
|
}
|
|
11308
11520
|
} catch (error) {
|
|
11309
11521
|
if (epoch === this.speechEpoch) this.patch({ error: message(error) });
|
|
@@ -11323,12 +11535,11 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11323
11535
|
if (this.speechLaunchPending) {
|
|
11324
11536
|
const epoch2 = this.speechEpoch;
|
|
11325
11537
|
const control2 = ++this.controlEpoch;
|
|
11326
|
-
|
|
11327
|
-
await this.
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
}
|
|
11538
|
+
try {
|
|
11539
|
+
await this._gateResumedSpeechInput();
|
|
11540
|
+
} catch (error) {
|
|
11541
|
+
if (epoch2 === this.speechEpoch) this.patch({ error: message(error) });
|
|
11542
|
+
return;
|
|
11332
11543
|
}
|
|
11333
11544
|
if (epoch2 === this.speechEpoch && control2 === this.controlEpoch)
|
|
11334
11545
|
this.patch({ paused: false });
|
|
@@ -11337,19 +11548,19 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11337
11548
|
const epoch = this.speechEpoch;
|
|
11338
11549
|
const control = ++this.controlEpoch;
|
|
11339
11550
|
try {
|
|
11340
|
-
|
|
11341
|
-
await this.stopListening();
|
|
11342
|
-
if (this.inputReleaseError) throw this.inputReleaseError;
|
|
11343
|
-
}
|
|
11551
|
+
await this._gateResumedSpeechInput();
|
|
11344
11552
|
if (epoch !== this.speechEpoch || control !== this.controlEpoch) return;
|
|
11345
11553
|
const result = await this.engines[this.snapshot.speechEngine || this.snapshot.settings.engine].resume();
|
|
11346
11554
|
if (epoch === this.speechEpoch && control === this.controlEpoch && result !== false) {
|
|
11347
11555
|
this.patch({ paused: false });
|
|
11348
11556
|
this.activeRecordingTimeline?.("assistant-resume");
|
|
11349
11557
|
this._prefetchSpeech();
|
|
11558
|
+
} else {
|
|
11559
|
+
await this._restorePausedSpeechInput(epoch, control);
|
|
11350
11560
|
}
|
|
11351
11561
|
} catch (error) {
|
|
11352
11562
|
if (epoch === this.speechEpoch) this.patch({ error: message(error) });
|
|
11563
|
+
await this._restorePausedSpeechInput(epoch, control);
|
|
11353
11564
|
}
|
|
11354
11565
|
}
|
|
11355
11566
|
async speak(text, messageId = null) {
|
|
@@ -11429,15 +11640,22 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11429
11640
|
if (epoch !== this.speechEpoch || this.disposed) return;
|
|
11430
11641
|
if (this.snapshot.settings.mode === "speaker") {
|
|
11431
11642
|
const gating = this.snapshot.listening;
|
|
11643
|
+
const persistentCapture = typeof this.recognition.suspendCapture === "function";
|
|
11432
11644
|
if (gating) timeline?.("capture-gated");
|
|
11433
|
-
|
|
11434
|
-
|
|
11645
|
+
try {
|
|
11646
|
+
const suspended = persistentCapture ? await this._suspendInputCapture() : false;
|
|
11647
|
+
if (gating && !suspended) {
|
|
11648
|
+
await this.stopListening();
|
|
11649
|
+
if (this.inputReleaseError) throw this.inputReleaseError;
|
|
11650
|
+
}
|
|
11651
|
+
} catch (error) {
|
|
11435
11652
|
if (gating) timeline?.("capture-gate-failed");
|
|
11436
|
-
throw
|
|
11653
|
+
throw error;
|
|
11437
11654
|
}
|
|
11438
11655
|
if (gating) timeline?.("input-stop-complete");
|
|
11439
11656
|
}
|
|
11440
11657
|
if (epoch !== this.speechEpoch || this.disposed) return;
|
|
11658
|
+
if (this.snapshot.paused) await this._restorePausedSpeechInput(epoch, this.controlEpoch);
|
|
11441
11659
|
const queued = item;
|
|
11442
11660
|
if (queued?.preparing) await queued.preparing;
|
|
11443
11661
|
if (epoch !== this.speechEpoch || this.disposed) return;
|
|
@@ -11552,6 +11770,12 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11552
11770
|
this.assistantSpeechDelayReason = "segment-gap";
|
|
11553
11771
|
this.assistantSpeechNotBefore = Date.now() + this.snapshot.settings.segmentGapMs;
|
|
11554
11772
|
this._drain();
|
|
11773
|
+
} else if (this.snapshot.settings.mode === "speaker" && this.snapshot.listening) {
|
|
11774
|
+
try {
|
|
11775
|
+
await this._resumeInputCapture();
|
|
11776
|
+
} catch (error) {
|
|
11777
|
+
this.patch({ inputError: message(error), error: message(error) });
|
|
11778
|
+
}
|
|
11555
11779
|
} else if (this.snapshot.conversation && !this.snapshot.inputRecovery && !this.snapshot.listening && !this.snapshot.starting)
|
|
11556
11780
|
await this._startInput(true);
|
|
11557
11781
|
}
|
|
@@ -11664,16 +11888,16 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11664
11888
|
if (boundary <= 0) return;
|
|
11665
11889
|
if (!complete && hasIncompleteSpeechMarkdown(remaining.slice(0, boundary))) return;
|
|
11666
11890
|
if (hasUnclosedCodeFence(remaining.slice(0, boundary)) && !complete) return;
|
|
11667
|
-
const
|
|
11891
|
+
const segments = splitSpeechOutput(remaining.slice(0, boundary).trim(), {
|
|
11668
11892
|
filterCodeBlocks: this.snapshot.settings.outputCodeFilterEnabled,
|
|
11669
11893
|
codeBlockMaxLines: this.snapshot.settings.outputCodeMaxLines,
|
|
11670
11894
|
codeBlockNotice: this.snapshot.settings.outputCodeNotice,
|
|
11671
11895
|
lang: this.snapshot.settings.lang
|
|
11672
|
-
})
|
|
11896
|
+
});
|
|
11673
11897
|
this.consumed.set(id, offset + boundary);
|
|
11674
|
-
if (
|
|
11898
|
+
if (segments.length) {
|
|
11675
11899
|
const initial = !this.snapshot.speaking && !this.queue.some((item) => item.prepared);
|
|
11676
|
-
const parts = initial ? splitInitialSpeechItem(
|
|
11900
|
+
const parts = initial ? [...splitInitialSpeechItem(segments[0]), ...segments.slice(1)] : segments;
|
|
11677
11901
|
this.queue.push(...parts.map((text2) => ({ text: text2, id })));
|
|
11678
11902
|
this._prefetchSpeech();
|
|
11679
11903
|
this._syncSpeechSegments();
|
|
@@ -11834,13 +12058,11 @@ var MeetingController = class {
|
|
|
11834
12058
|
for (const input of this.inputs.values()) input.cancelAutoSend();
|
|
11835
12059
|
}
|
|
11836
12060
|
composerReplaced() {
|
|
12061
|
+
for (const [source, job] of this.jobs) {
|
|
12062
|
+
const state = this.snapshot[source];
|
|
12063
|
+
job.discardResults = (Number.isSafeInteger(state?.pendingTranscriptionRequests) ? state.pendingTranscriptionRequests : state?.pending || 0) || (state?.recognizing ? 1 : 0);
|
|
12064
|
+
}
|
|
11837
12065
|
this.cancelDelivery();
|
|
11838
|
-
const job = this.jobs.get("shared");
|
|
11839
|
-
if (!job || !job.recognitionAbort) return;
|
|
11840
|
-
job.recognitionAbort.abort();
|
|
11841
|
-
if (this.sharedTrackLive(job)) this.queueRecognitionRecovery("shared", job);
|
|
11842
|
-
else void this.stop("shared").catch(() => {
|
|
11843
|
-
});
|
|
11844
12066
|
}
|
|
11845
12067
|
toggleTimestamps() {
|
|
11846
12068
|
const enabled = !this.snapshot.timestamps;
|
|
@@ -11864,7 +12086,7 @@ var MeetingController = class {
|
|
|
11864
12086
|
}
|
|
11865
12087
|
this.patch(null, { active: true });
|
|
11866
12088
|
const abort = new AbortController();
|
|
11867
|
-
const job = { abort };
|
|
12089
|
+
const job = { abort, discardResults: 0 };
|
|
11868
12090
|
this.jobs.set(source, job);
|
|
11869
12091
|
const valid = () => this.jobs.get(source) === job && !abort.signal.aborted;
|
|
11870
12092
|
this.patch(source, { starting: true, error: null });
|
|
@@ -11934,6 +12156,10 @@ var MeetingController = class {
|
|
|
11934
12156
|
signal: recognitionAbort.signal,
|
|
11935
12157
|
onResult: ({ final, interim, startedAt, paragraphBefore }) => {
|
|
11936
12158
|
if (!valid()) return;
|
|
12159
|
+
if (job.discardResults > 0) {
|
|
12160
|
+
job.discardResults--;
|
|
12161
|
+
return;
|
|
12162
|
+
}
|
|
11937
12163
|
if (final?.trim()) job.retries = 0;
|
|
11938
12164
|
const accepted = this.onSpeech?.(source, final || interim) !== false;
|
|
11939
12165
|
if (accepted)
|
|
@@ -12770,7 +12996,6 @@ var AttendeeClient = class {
|
|
|
12770
12996
|
this.queues.set(source, work);
|
|
12771
12997
|
}
|
|
12772
12998
|
async audio(event, generation) {
|
|
12773
|
-
if (this.canDeliver && !this.canDeliver()) throw new Error("no-composer");
|
|
12774
12999
|
const source = "attendee:" + event.botId + ":" + event.participantId;
|
|
12775
13000
|
let row = this.rows.get(source);
|
|
12776
13001
|
if (!row) {
|
|
@@ -14576,8 +14801,8 @@ function createDeviceOptions(devices, kind, systemDefault, fallback) {
|
|
|
14576
14801
|
var import_react39 = __toESM(require("react"), 1);
|
|
14577
14802
|
|
|
14578
14803
|
// src/modules/settings/services/releases.ts
|
|
14579
|
-
var CURRENT_VERSION = "0.7.
|
|
14580
|
-
var TESTED_DSH_VERSION = "0.2.2-
|
|
14804
|
+
var CURRENT_VERSION = "0.7.2";
|
|
14805
|
+
var TESTED_DSH_VERSION = "0.2.2-rc.1";
|
|
14581
14806
|
var REPOSITORY_URL = "https://github.com/victorwads/dsh-live-voice";
|
|
14582
14807
|
var RELEASES_URL = `${REPOSITORY_URL}/releases`;
|
|
14583
14808
|
var LATEST_RELEASE_API_URL = "https://api.github.com/repos/victorwads/dsh-live-voice/releases/latest";
|
|
@@ -17674,7 +17899,9 @@ function apply(ctx) {
|
|
|
17674
17899
|
entry.speechState.checkpoint = entry.controller.checkpointSpeech();
|
|
17675
17900
|
entry.speechAttached = false;
|
|
17676
17901
|
++entry.speechRequest;
|
|
17677
|
-
const
|
|
17902
|
+
const preserveInput = entry.controller.snapshot.conversation && entry.controller.snapshot.listening && typeof entry.controller.recognition.pauseForNavigation === "function";
|
|
17903
|
+
entry.parkedInput = preserveInput;
|
|
17904
|
+
const stopped = entry.controller.suspendSpeech(preserveInput);
|
|
17678
17905
|
entry.speechRelease = Promise.resolve(stopped).then(() => {
|
|
17679
17906
|
if (entry.controller.speechStopError) throw entry.controller.speechStopError;
|
|
17680
17907
|
if (entry.controller.inputReleaseError) throw entry.controller.inputReleaseError;
|
|
@@ -17694,6 +17921,13 @@ function apply(ctx) {
|
|
|
17694
17921
|
await entry.speechRelease;
|
|
17695
17922
|
if (disposed || entry.closed || !entry.speechAttached || request !== entry.speechRequest)
|
|
17696
17923
|
return;
|
|
17924
|
+
if (entry.parkedInput) {
|
|
17925
|
+
const resumed = await entry.controller.resumeInputFromNavigation();
|
|
17926
|
+
if (disposed || entry.closed || !entry.speechAttached || request !== entry.speechRequest)
|
|
17927
|
+
return;
|
|
17928
|
+
if (!resumed) throw new Error("The paused microphone recording could not resume.");
|
|
17929
|
+
entry.parkedInput = false;
|
|
17930
|
+
}
|
|
17697
17931
|
entry.controller.restoreSpeech(entry.speechState.checkpoint);
|
|
17698
17932
|
entry.speechState.checkpoint = null;
|
|
17699
17933
|
entry.refreshSpeech(!entry.speechState.attached);
|
|
@@ -17704,6 +17938,7 @@ function apply(ctx) {
|
|
|
17704
17938
|
function retire(entry) {
|
|
17705
17939
|
if (entry.closed) return;
|
|
17706
17940
|
suspendSessionSpeech(entry);
|
|
17941
|
+
if (entry.parkedInput && !disposed) return;
|
|
17707
17942
|
entry.closed = true;
|
|
17708
17943
|
if (meetingEntry === entry) {
|
|
17709
17944
|
void meeting.attendee.stop();
|
|
@@ -17878,7 +18113,7 @@ function apply(ctx) {
|
|
|
17878
18113
|
entry.chatListeners.add(listener);
|
|
17879
18114
|
return () => entry.chatListeners.delete(listener);
|
|
17880
18115
|
};
|
|
17881
|
-
entry.readChat = entry.chat.getSnapshot
|
|
18116
|
+
entry.readChat = () => entry.chat.getSnapshot();
|
|
17882
18117
|
const refresh = (reset = false) => {
|
|
17883
18118
|
if (disposed || entry.closed) return;
|
|
17884
18119
|
const snapshot = entry.readChat();
|
|
@@ -17906,10 +18141,20 @@ function apply(ctx) {
|
|
|
17906
18141
|
};
|
|
17907
18142
|
entry.refreshSpeech = refresh;
|
|
17908
18143
|
refresh();
|
|
17909
|
-
|
|
18144
|
+
const publishChat = () => {
|
|
17910
18145
|
refresh();
|
|
17911
18146
|
for (const listener of entry.chatListeners) listener();
|
|
17912
|
-
}
|
|
18147
|
+
};
|
|
18148
|
+
entry.unsubscribe = entry.chat.subscribe(publishChat);
|
|
18149
|
+
entry.reconnectChat = () => {
|
|
18150
|
+
if (disposed || entry.closed) return;
|
|
18151
|
+
const chat = ctx.uiConversation.binding(sessionId).target("chat");
|
|
18152
|
+
if (chat === entry.chat) return;
|
|
18153
|
+
entry.unsubscribe?.();
|
|
18154
|
+
entry.chat = chat;
|
|
18155
|
+
entry.unsubscribe = chat.subscribe(publishChat);
|
|
18156
|
+
publishChat();
|
|
18157
|
+
};
|
|
17913
18158
|
const pendingInteractions = ctx.uiSession.pendingInteractions;
|
|
17914
18159
|
const refreshPendingQuestion = (baseline = false) => {
|
|
17915
18160
|
if (disposed || entry.closed || !pendingInteractions) return;
|
|
@@ -17941,7 +18186,7 @@ function apply(ctx) {
|
|
|
17941
18186
|
const before = controller.getSnapshot();
|
|
17942
18187
|
update(next);
|
|
17943
18188
|
const settings2 = controller.getSnapshot().settings;
|
|
17944
|
-
if ([...recognitionSettingKeys].some((key2) => before.settings[key2] !== settings2[key2])) {
|
|
18189
|
+
if (!entry.parkedInput && [...recognitionSettingKeys].some((key2) => before.settings[key2] !== settings2[key2])) {
|
|
17945
18190
|
if (!entry.restoreInput) entry.inputRestoreRequest = entry.request;
|
|
17946
18191
|
entry.restoreInput ||= Boolean(before.listening || before.starting);
|
|
17947
18192
|
const revision = entry.inputSettingsRevision = (entry.inputSettingsRevision || 0) + 1;
|
|
@@ -17983,6 +18228,7 @@ function apply(ctx) {
|
|
|
17983
18228
|
const original = controller[method].bind(controller);
|
|
17984
18229
|
controller[method] = (...args) => {
|
|
17985
18230
|
if (method === "endConversation" && args[0] === true && !entry.speechAttached) {
|
|
18231
|
+
if (entry.parkedInput) return entry.speechRelease ?? Promise.resolve();
|
|
17986
18232
|
controller.patch({ conversation: false });
|
|
17987
18233
|
return Promise.all([
|
|
17988
18234
|
controller.stopListening(),
|
|
@@ -18004,6 +18250,7 @@ function apply(ctx) {
|
|
|
18004
18250
|
}
|
|
18005
18251
|
}
|
|
18006
18252
|
entry.request++;
|
|
18253
|
+
if (method !== "stopSpeech") entry.parkedInput = false;
|
|
18007
18254
|
if (method === "endConversation" && args[0] !== true || method === "stopConversationInput")
|
|
18008
18255
|
voiceModeActive = false;
|
|
18009
18256
|
const result = original(...args);
|
|
@@ -18084,6 +18331,7 @@ function apply(ctx) {
|
|
|
18084
18331
|
import_react75.default.useLayoutEffect(() => {
|
|
18085
18332
|
if (disposed) return;
|
|
18086
18333
|
const current = get(sessionId);
|
|
18334
|
+
current.reconnectChat();
|
|
18087
18335
|
current.refs++;
|
|
18088
18336
|
if (kind === "buttons") current.buttons++;
|
|
18089
18337
|
setEntry(current);
|
|
@@ -18108,7 +18356,6 @@ function apply(ctx) {
|
|
|
18108
18356
|
entry.composers.delete(token.current);
|
|
18109
18357
|
if (!entry.composers.size) suspendSessionSpeech(entry);
|
|
18110
18358
|
if (meetingEntry === entry && !entry.composers.size) {
|
|
18111
|
-
void meeting.attendee.stop();
|
|
18112
18359
|
meeting.composerReplaced();
|
|
18113
18360
|
meetingEntry = null;
|
|
18114
18361
|
meeting.transcript.setActive("microphone", false);
|
|
@@ -18119,10 +18366,7 @@ function apply(ctx) {
|
|
|
18119
18366
|
if (!entry || entry.closed || disposed) return;
|
|
18120
18367
|
if (!input || typeof props.inputActions?.setDraft !== "function") return;
|
|
18121
18368
|
if (meetingEntry !== entry) {
|
|
18122
|
-
if (meetingEntry)
|
|
18123
|
-
void meeting.attendee.stop();
|
|
18124
|
-
suspendSessionSpeech(meetingEntry);
|
|
18125
|
-
}
|
|
18369
|
+
if (meetingEntry) suspendSessionSpeech(meetingEntry);
|
|
18126
18370
|
meeting.composerReplaced();
|
|
18127
18371
|
meetingEntry = entry;
|
|
18128
18372
|
entry.controller.setExternalSpeechActivity(sharedSpeechActive);
|
|
@@ -18154,7 +18398,7 @@ function apply(ctx) {
|
|
|
18154
18398
|
});
|
|
18155
18399
|
const attachSpeech = !entry.speechAttached;
|
|
18156
18400
|
if (attachSpeech) entry.speechAttached = true;
|
|
18157
|
-
const inputReady = voiceModeActive && !entry.controller.getSnapshot().conversation ? entry.controller.startConversation() : Promise.resolve();
|
|
18401
|
+
const inputReady = !entry.parkedInput && voiceModeActive && !entry.controller.getSnapshot().conversation ? entry.controller.startConversation() : Promise.resolve();
|
|
18158
18402
|
run(
|
|
18159
18403
|
entry.controller,
|
|
18160
18404
|
Promise.resolve(inputReady).then(() => {
|