dsh-live-voice 0.7.0 → 0.7.1
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 +238 -37
- 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 +1 -0
- package/src/app/client/apply.tsx +22 -8
- 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 +138 -13
- 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/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Local-first speech-to-text (STT), text-to-speech (TTS), and meeting audio transc
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
Tested with **DeepSeek Harness v0.2.
|
|
7
|
+
Tested with **DeepSeek Harness v0.2.2-rc.1**. The package manifest's `dshTestedVersion` field records the tested version.
|
|
8
8
|
|
|
9
9
|
Install the prebuilt package from npm; no source build is required. The Git repository contains source only.
|
|
10
10
|
|
package/lib/asr/asr.worker.js
CHANGED
|
@@ -36682,7 +36682,7 @@ async function createQwenRuntime(options) {
|
|
|
36682
36682
|
}
|
|
36683
36683
|
|
|
36684
36684
|
// src/modules/recognition/local/assets.ts
|
|
36685
|
-
var browserAsrAssetBase = () => "/api/dsh-live-voice/asr/" + encodeURIComponent(true ? "0.7.
|
|
36685
|
+
var browserAsrAssetBase = () => "/api/dsh-live-voice/asr/" + encodeURIComponent(true ? "0.7.1" : "test") + "/";
|
|
36686
36686
|
|
|
36687
36687
|
// src/modules/recognition/local/modelCatalog.ts
|
|
36688
36688
|
var browserAsrModels = [
|
package/lib/asr/manifest.json
CHANGED
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.1" : "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.1" : "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() {
|
|
10666
|
+
return this._input(async () => {
|
|
10667
|
+
if (!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
|
}
|
|
@@ -11337,7 +11517,7 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11337
11517
|
const epoch = this.speechEpoch;
|
|
11338
11518
|
const control = ++this.controlEpoch;
|
|
11339
11519
|
try {
|
|
11340
|
-
if (this.snapshot.settings.mode === "speaker") {
|
|
11520
|
+
if (this.snapshot.settings.mode === "speaker" && !this._hasSuspendedInputCapture()) {
|
|
11341
11521
|
await this.stopListening();
|
|
11342
11522
|
if (this.inputReleaseError) throw this.inputReleaseError;
|
|
11343
11523
|
}
|
|
@@ -11429,11 +11609,17 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11429
11609
|
if (epoch !== this.speechEpoch || this.disposed) return;
|
|
11430
11610
|
if (this.snapshot.settings.mode === "speaker") {
|
|
11431
11611
|
const gating = this.snapshot.listening;
|
|
11612
|
+
const persistentCapture = typeof this.recognition.suspendCapture === "function";
|
|
11432
11613
|
if (gating) timeline?.("capture-gated");
|
|
11433
|
-
|
|
11434
|
-
|
|
11614
|
+
try {
|
|
11615
|
+
const suspended = persistentCapture ? await this._suspendInputCapture() : false;
|
|
11616
|
+
if (gating && !suspended) {
|
|
11617
|
+
await this.stopListening();
|
|
11618
|
+
if (this.inputReleaseError) throw this.inputReleaseError;
|
|
11619
|
+
}
|
|
11620
|
+
} catch (error) {
|
|
11435
11621
|
if (gating) timeline?.("capture-gate-failed");
|
|
11436
|
-
throw
|
|
11622
|
+
throw error;
|
|
11437
11623
|
}
|
|
11438
11624
|
if (gating) timeline?.("input-stop-complete");
|
|
11439
11625
|
}
|
|
@@ -11552,6 +11738,12 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11552
11738
|
this.assistantSpeechDelayReason = "segment-gap";
|
|
11553
11739
|
this.assistantSpeechNotBefore = Date.now() + this.snapshot.settings.segmentGapMs;
|
|
11554
11740
|
this._drain();
|
|
11741
|
+
} else if (this.snapshot.settings.mode === "speaker" && this.snapshot.listening) {
|
|
11742
|
+
try {
|
|
11743
|
+
await this._resumeInputCapture();
|
|
11744
|
+
} catch (error) {
|
|
11745
|
+
this.patch({ inputError: message(error), error: message(error) });
|
|
11746
|
+
}
|
|
11555
11747
|
} else if (this.snapshot.conversation && !this.snapshot.inputRecovery && !this.snapshot.listening && !this.snapshot.starting)
|
|
11556
11748
|
await this._startInput(true);
|
|
11557
11749
|
}
|
|
@@ -11664,16 +11856,16 @@ var VoiceCoordinator = class _VoiceCoordinator {
|
|
|
11664
11856
|
if (boundary <= 0) return;
|
|
11665
11857
|
if (!complete && hasIncompleteSpeechMarkdown(remaining.slice(0, boundary))) return;
|
|
11666
11858
|
if (hasUnclosedCodeFence(remaining.slice(0, boundary)) && !complete) return;
|
|
11667
|
-
const
|
|
11859
|
+
const segments = splitSpeechOutput(remaining.slice(0, boundary).trim(), {
|
|
11668
11860
|
filterCodeBlocks: this.snapshot.settings.outputCodeFilterEnabled,
|
|
11669
11861
|
codeBlockMaxLines: this.snapshot.settings.outputCodeMaxLines,
|
|
11670
11862
|
codeBlockNotice: this.snapshot.settings.outputCodeNotice,
|
|
11671
11863
|
lang: this.snapshot.settings.lang
|
|
11672
|
-
})
|
|
11864
|
+
});
|
|
11673
11865
|
this.consumed.set(id, offset + boundary);
|
|
11674
|
-
if (
|
|
11866
|
+
if (segments.length) {
|
|
11675
11867
|
const initial = !this.snapshot.speaking && !this.queue.some((item) => item.prepared);
|
|
11676
|
-
const parts = initial ? splitInitialSpeechItem(
|
|
11868
|
+
const parts = initial ? [...splitInitialSpeechItem(segments[0]), ...segments.slice(1)] : segments;
|
|
11677
11869
|
this.queue.push(...parts.map((text2) => ({ text: text2, id })));
|
|
11678
11870
|
this._prefetchSpeech();
|
|
11679
11871
|
this._syncSpeechSegments();
|
|
@@ -11834,13 +12026,11 @@ var MeetingController = class {
|
|
|
11834
12026
|
for (const input of this.inputs.values()) input.cancelAutoSend();
|
|
11835
12027
|
}
|
|
11836
12028
|
composerReplaced() {
|
|
12029
|
+
for (const [source, job] of this.jobs) {
|
|
12030
|
+
const state = this.snapshot[source];
|
|
12031
|
+
job.discardResults = (Number.isSafeInteger(state?.pendingTranscriptionRequests) ? state.pendingTranscriptionRequests : state?.pending || 0) || (state?.recognizing ? 1 : 0);
|
|
12032
|
+
}
|
|
11837
12033
|
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
12034
|
}
|
|
11845
12035
|
toggleTimestamps() {
|
|
11846
12036
|
const enabled = !this.snapshot.timestamps;
|
|
@@ -11864,7 +12054,7 @@ var MeetingController = class {
|
|
|
11864
12054
|
}
|
|
11865
12055
|
this.patch(null, { active: true });
|
|
11866
12056
|
const abort = new AbortController();
|
|
11867
|
-
const job = { abort };
|
|
12057
|
+
const job = { abort, discardResults: 0 };
|
|
11868
12058
|
this.jobs.set(source, job);
|
|
11869
12059
|
const valid = () => this.jobs.get(source) === job && !abort.signal.aborted;
|
|
11870
12060
|
this.patch(source, { starting: true, error: null });
|
|
@@ -11934,6 +12124,10 @@ var MeetingController = class {
|
|
|
11934
12124
|
signal: recognitionAbort.signal,
|
|
11935
12125
|
onResult: ({ final, interim, startedAt, paragraphBefore }) => {
|
|
11936
12126
|
if (!valid()) return;
|
|
12127
|
+
if (job.discardResults > 0) {
|
|
12128
|
+
job.discardResults--;
|
|
12129
|
+
return;
|
|
12130
|
+
}
|
|
11937
12131
|
if (final?.trim()) job.retries = 0;
|
|
11938
12132
|
const accepted = this.onSpeech?.(source, final || interim) !== false;
|
|
11939
12133
|
if (accepted)
|
|
@@ -12770,7 +12964,6 @@ var AttendeeClient = class {
|
|
|
12770
12964
|
this.queues.set(source, work);
|
|
12771
12965
|
}
|
|
12772
12966
|
async audio(event, generation) {
|
|
12773
|
-
if (this.canDeliver && !this.canDeliver()) throw new Error("no-composer");
|
|
12774
12967
|
const source = "attendee:" + event.botId + ":" + event.participantId;
|
|
12775
12968
|
let row = this.rows.get(source);
|
|
12776
12969
|
if (!row) {
|
|
@@ -14576,8 +14769,8 @@ function createDeviceOptions(devices, kind, systemDefault, fallback) {
|
|
|
14576
14769
|
var import_react39 = __toESM(require("react"), 1);
|
|
14577
14770
|
|
|
14578
14771
|
// src/modules/settings/services/releases.ts
|
|
14579
|
-
var CURRENT_VERSION = "0.7.
|
|
14580
|
-
var TESTED_DSH_VERSION = "0.2.2-
|
|
14772
|
+
var CURRENT_VERSION = "0.7.1";
|
|
14773
|
+
var TESTED_DSH_VERSION = "0.2.2-rc.1";
|
|
14581
14774
|
var REPOSITORY_URL = "https://github.com/victorwads/dsh-live-voice";
|
|
14582
14775
|
var RELEASES_URL = `${REPOSITORY_URL}/releases`;
|
|
14583
14776
|
var LATEST_RELEASE_API_URL = "https://api.github.com/repos/victorwads/dsh-live-voice/releases/latest";
|
|
@@ -17674,7 +17867,9 @@ function apply(ctx) {
|
|
|
17674
17867
|
entry.speechState.checkpoint = entry.controller.checkpointSpeech();
|
|
17675
17868
|
entry.speechAttached = false;
|
|
17676
17869
|
++entry.speechRequest;
|
|
17677
|
-
const
|
|
17870
|
+
const preserveInput = entry.controller.snapshot.conversation && entry.controller.snapshot.listening && typeof entry.controller.recognition.pauseForNavigation === "function";
|
|
17871
|
+
entry.parkedInput = preserveInput;
|
|
17872
|
+
const stopped = entry.controller.suspendSpeech(preserveInput);
|
|
17678
17873
|
entry.speechRelease = Promise.resolve(stopped).then(() => {
|
|
17679
17874
|
if (entry.controller.speechStopError) throw entry.controller.speechStopError;
|
|
17680
17875
|
if (entry.controller.inputReleaseError) throw entry.controller.inputReleaseError;
|
|
@@ -17694,6 +17889,13 @@ function apply(ctx) {
|
|
|
17694
17889
|
await entry.speechRelease;
|
|
17695
17890
|
if (disposed || entry.closed || !entry.speechAttached || request !== entry.speechRequest)
|
|
17696
17891
|
return;
|
|
17892
|
+
if (entry.parkedInput) {
|
|
17893
|
+
const resumed = await entry.controller.resumeInputFromNavigation();
|
|
17894
|
+
if (disposed || entry.closed || !entry.speechAttached || request !== entry.speechRequest)
|
|
17895
|
+
return;
|
|
17896
|
+
if (!resumed) throw new Error("The paused microphone recording could not resume.");
|
|
17897
|
+
entry.parkedInput = false;
|
|
17898
|
+
}
|
|
17697
17899
|
entry.controller.restoreSpeech(entry.speechState.checkpoint);
|
|
17698
17900
|
entry.speechState.checkpoint = null;
|
|
17699
17901
|
entry.refreshSpeech(!entry.speechState.attached);
|
|
@@ -17704,6 +17906,7 @@ function apply(ctx) {
|
|
|
17704
17906
|
function retire(entry) {
|
|
17705
17907
|
if (entry.closed) return;
|
|
17706
17908
|
suspendSessionSpeech(entry);
|
|
17909
|
+
if (entry.parkedInput && !disposed) return;
|
|
17707
17910
|
entry.closed = true;
|
|
17708
17911
|
if (meetingEntry === entry) {
|
|
17709
17912
|
void meeting.attendee.stop();
|
|
@@ -17941,7 +18144,7 @@ function apply(ctx) {
|
|
|
17941
18144
|
const before = controller.getSnapshot();
|
|
17942
18145
|
update(next);
|
|
17943
18146
|
const settings2 = controller.getSnapshot().settings;
|
|
17944
|
-
if ([...recognitionSettingKeys].some((key2) => before.settings[key2] !== settings2[key2])) {
|
|
18147
|
+
if (!entry.parkedInput && [...recognitionSettingKeys].some((key2) => before.settings[key2] !== settings2[key2])) {
|
|
17945
18148
|
if (!entry.restoreInput) entry.inputRestoreRequest = entry.request;
|
|
17946
18149
|
entry.restoreInput ||= Boolean(before.listening || before.starting);
|
|
17947
18150
|
const revision = entry.inputSettingsRevision = (entry.inputSettingsRevision || 0) + 1;
|
|
@@ -17983,6 +18186,7 @@ function apply(ctx) {
|
|
|
17983
18186
|
const original = controller[method].bind(controller);
|
|
17984
18187
|
controller[method] = (...args) => {
|
|
17985
18188
|
if (method === "endConversation" && args[0] === true && !entry.speechAttached) {
|
|
18189
|
+
if (entry.parkedInput) return entry.speechRelease ?? Promise.resolve();
|
|
17986
18190
|
controller.patch({ conversation: false });
|
|
17987
18191
|
return Promise.all([
|
|
17988
18192
|
controller.stopListening(),
|
|
@@ -18004,6 +18208,7 @@ function apply(ctx) {
|
|
|
18004
18208
|
}
|
|
18005
18209
|
}
|
|
18006
18210
|
entry.request++;
|
|
18211
|
+
if (method !== "stopSpeech") entry.parkedInput = false;
|
|
18007
18212
|
if (method === "endConversation" && args[0] !== true || method === "stopConversationInput")
|
|
18008
18213
|
voiceModeActive = false;
|
|
18009
18214
|
const result = original(...args);
|
|
@@ -18108,7 +18313,6 @@ function apply(ctx) {
|
|
|
18108
18313
|
entry.composers.delete(token.current);
|
|
18109
18314
|
if (!entry.composers.size) suspendSessionSpeech(entry);
|
|
18110
18315
|
if (meetingEntry === entry && !entry.composers.size) {
|
|
18111
|
-
void meeting.attendee.stop();
|
|
18112
18316
|
meeting.composerReplaced();
|
|
18113
18317
|
meetingEntry = null;
|
|
18114
18318
|
meeting.transcript.setActive("microphone", false);
|
|
@@ -18119,10 +18323,7 @@ function apply(ctx) {
|
|
|
18119
18323
|
if (!entry || entry.closed || disposed) return;
|
|
18120
18324
|
if (!input || typeof props.inputActions?.setDraft !== "function") return;
|
|
18121
18325
|
if (meetingEntry !== entry) {
|
|
18122
|
-
if (meetingEntry)
|
|
18123
|
-
void meeting.attendee.stop();
|
|
18124
|
-
suspendSessionSpeech(meetingEntry);
|
|
18125
|
-
}
|
|
18326
|
+
if (meetingEntry) suspendSessionSpeech(meetingEntry);
|
|
18126
18327
|
meeting.composerReplaced();
|
|
18127
18328
|
meetingEntry = entry;
|
|
18128
18329
|
entry.controller.setExternalSpeechActivity(sharedSpeechActive);
|
|
@@ -18154,7 +18355,7 @@ function apply(ctx) {
|
|
|
18154
18355
|
});
|
|
18155
18356
|
const attachSpeech = !entry.speechAttached;
|
|
18156
18357
|
if (attachSpeech) entry.speechAttached = true;
|
|
18157
|
-
const inputReady = voiceModeActive && !entry.controller.getSnapshot().conversation ? entry.controller.startConversation() : Promise.resolve();
|
|
18358
|
+
const inputReady = !entry.parkedInput && voiceModeActive && !entry.controller.getSnapshot().conversation ? entry.controller.startConversation() : Promise.resolve();
|
|
18158
18359
|
run(
|
|
18159
18360
|
entry.controller,
|
|
18160
18361
|
Promise.resolve(inputReady).then(() => {
|
package/lib/server.js
CHANGED
|
@@ -2052,7 +2052,7 @@ var VAD_ASSET_TYPES = {
|
|
|
2052
2052
|
};
|
|
2053
2053
|
var MAX_VAD_ASSET_BYTES = 32 * 1024 * 1024;
|
|
2054
2054
|
function registerVadAssetRoutes(ctx, options = {}) {
|
|
2055
|
-
const version = options.version ?? (true ? "0.7.
|
|
2055
|
+
const version = options.version ?? (true ? "0.7.1" : "test");
|
|
2056
2056
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9.+-]{0,127}$/.test(version))
|
|
2057
2057
|
throw new Error("Invalid VAD asset version");
|
|
2058
2058
|
const directory = options.directory ?? fileURLToPath(new URL("./vad/", import.meta.url));
|
|
@@ -2146,7 +2146,7 @@ import { lstat as lstat2, readFile as readFile5 } from "node:fs/promises";
|
|
|
2146
2146
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2147
2147
|
import { join as join8 } from "node:path";
|
|
2148
2148
|
function registerBrowserAsrAssets(ctx, options = {}) {
|
|
2149
|
-
const version = options.version ?? (true ? "0.7.
|
|
2149
|
+
const version = options.version ?? (true ? "0.7.1" : "test");
|
|
2150
2150
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9.+-]{0,127}$/.test(version)) throw new Error("invalid-version");
|
|
2151
2151
|
const base = "/api/dsh-live-voice/asr/" + encodeURIComponent(version) + "/";
|
|
2152
2152
|
const directory = options.directory ?? fileURLToPath2(new URL("./asr/", import.meta.url));
|
package/lib/vad/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"pluginVersion": "0.7.
|
|
3
|
+
"pluginVersion": "0.7.1",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@ricky0123/vad-web": "0.0.31",
|
|
6
6
|
"onnxruntime-web": "1.22.0"
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
"sha256": "2623a2953f6ff3d2c1e61740c6cdb7168133479b267dfef114a4a3cc5bdd788f"
|
|
37
37
|
},
|
|
38
38
|
"vad.capture.js": {
|
|
39
|
-
"bytes":
|
|
40
|
-
"sha256": "
|
|
39
|
+
"bytes": 1694,
|
|
40
|
+
"sha256": "d24df69dc6326d8c1b37437b0bef459a3a67405237108a3c32868a87c3fa13c4"
|
|
41
41
|
},
|
|
42
42
|
"vad.worker.js": {
|
|
43
43
|
"bytes": 79337,
|
|
44
|
-
"sha256": "
|
|
44
|
+
"sha256": "36a94fd4b60d3717d25628a033574d1b6c2fb576a8be732c87255ba567c33bd0"
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
}
|
package/lib/vad/vad.capture.js
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
// src/modules/recognition/vad/vad.capture.ts
|
|
2
2
|
var VadCaptureProcessor = class extends AudioWorkletProcessor {
|
|
3
3
|
stopped = false;
|
|
4
|
+
paused = false;
|
|
4
5
|
pcm = new Float32Array(2048);
|
|
5
6
|
at = 0;
|
|
6
7
|
constructor() {
|
|
7
8
|
super();
|
|
8
9
|
this.port.onmessage = (event) => {
|
|
10
|
+
if (event.data?.type === "resume") this.paused = false;
|
|
11
|
+
if (event.data?.type === "flush") {
|
|
12
|
+
if (event.data.pause) this.paused = true;
|
|
13
|
+
if (this.at) {
|
|
14
|
+
const pcm = this.pcm.slice(0, this.at);
|
|
15
|
+
this.port.postMessage({ pcm, sampleRate }, [pcm.buffer]);
|
|
16
|
+
this.at = 0;
|
|
17
|
+
}
|
|
18
|
+
this.port.postMessage({ type: "flushed" });
|
|
19
|
+
}
|
|
9
20
|
if (event.data?.type === "stop") {
|
|
10
21
|
this.stopped = true;
|
|
11
22
|
if (event.data.drain && this.at) {
|
|
@@ -20,6 +31,7 @@ var VadCaptureProcessor = class extends AudioWorkletProcessor {
|
|
|
20
31
|
process(inputs, outputs) {
|
|
21
32
|
for (const channels of outputs) for (const channel of channels) channel.fill(0);
|
|
22
33
|
if (this.stopped) return false;
|
|
34
|
+
if (this.paused) return true;
|
|
23
35
|
const input = inputs[0];
|
|
24
36
|
if (!input?.length) return true;
|
|
25
37
|
for (let i = 0; i < input[0].length; i++) {
|
package/lib/vad/vad.worker.js
CHANGED
|
@@ -1632,7 +1632,7 @@ var vo = Ke;
|
|
|
1632
1632
|
Object.defineProperty(I.versions, "web", { value: $t, enumerable: true });
|
|
1633
1633
|
|
|
1634
1634
|
// src/modules/recognition/vad/assets.ts
|
|
1635
|
-
var sileroVadAssetBase = () => "/api/dsh-live-voice/vad/" + encodeURIComponent(true ? "0.7.
|
|
1635
|
+
var sileroVadAssetBase = () => "/api/dsh-live-voice/vad/" + encodeURIComponent(true ? "0.7.1" : "test") + "/";
|
|
1636
1636
|
|
|
1637
1637
|
// src/modules/recognition/vad/StreamingResampler.ts
|
|
1638
1638
|
var StreamingResampler = class {
|