speech-to-speech 0.1.0

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.
@@ -0,0 +1,664 @@
1
+ import { getDefaultRealTimeVADOptions, MicVAD } from '@ricky0123/vad-web';
2
+
3
+ // src/stt/reset-stt-logic.ts
4
+ var ResetSTTLogic = class {
5
+ constructor(options = {}) {
6
+ this.partialTranscript = "";
7
+ this.maxSilenceMs = options.maxSilenceMs ?? 2e3;
8
+ this.maxUtteranceMs = options.maxUtteranceMs ?? 15e3;
9
+ this.onReset = options.onReset;
10
+ this.now = options.now ?? (() => Date.now());
11
+ const start = this.now();
12
+ this.utteranceStartedAt = start;
13
+ this.lastActivityAt = start;
14
+ }
15
+ recordSpeechActivity(timestamp) {
16
+ const now = timestamp ?? this.now();
17
+ this.lastActivityAt = now;
18
+ if (!this.utteranceStartedAt) {
19
+ this.utteranceStartedAt = now;
20
+ }
21
+ }
22
+ updatePartialTranscript(partial, timestamp) {
23
+ this.partialTranscript = partial;
24
+ this.recordSpeechActivity(timestamp);
25
+ }
26
+ shouldReset(timestamp) {
27
+ const now = timestamp ?? this.now();
28
+ const silenceElapsed = now - this.lastActivityAt;
29
+ const utteranceElapsed = now - this.utteranceStartedAt;
30
+ if (silenceElapsed >= this.maxSilenceMs) {
31
+ return "silence";
32
+ }
33
+ if (utteranceElapsed >= this.maxUtteranceMs) {
34
+ return "utterance-complete";
35
+ }
36
+ return null;
37
+ }
38
+ maybeReset(timestamp) {
39
+ const reason = this.shouldReset(timestamp);
40
+ if (reason) {
41
+ this.reset(reason, timestamp);
42
+ }
43
+ return reason;
44
+ }
45
+ forceReset(reason = "manual", timestamp) {
46
+ this.reset(reason, timestamp);
47
+ }
48
+ reset(reason, timestamp) {
49
+ const now = timestamp ?? this.now();
50
+ const stats = {
51
+ utteranceStartedAt: this.utteranceStartedAt,
52
+ lastActivityAt: this.lastActivityAt,
53
+ partialTranscript: this.partialTranscript
54
+ };
55
+ this.utteranceStartedAt = now;
56
+ this.lastActivityAt = now;
57
+ this.partialTranscript = "";
58
+ if (this.onReset) {
59
+ this.onReset(reason, stats);
60
+ }
61
+ }
62
+ };
63
+ var VADController = class {
64
+ constructor(options) {
65
+ this.vad = null;
66
+ this.voiceStartListeners = /* @__PURE__ */ new Set();
67
+ this.voiceStopListeners = /* @__PURE__ */ new Set();
68
+ this.running = false;
69
+ this.options = options;
70
+ }
71
+ async start() {
72
+ if (this.running && this.vad) {
73
+ if (!this.vad.listening) {
74
+ await this.vad.start();
75
+ }
76
+ return;
77
+ }
78
+ if (typeof navigator === "undefined" || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
79
+ throw new Error("Microphone access is not available.");
80
+ }
81
+ try {
82
+ const ortAny = window.ort;
83
+ if (ortAny && ortAny.env && ortAny.env.wasm) {
84
+ ortAny.env.wasm.wasmPaths = "/ort/";
85
+ }
86
+ if (!this.vad) {
87
+ const defaultOptions = getDefaultRealTimeVADOptions("v5");
88
+ this.vad = await MicVAD.new({
89
+ ...defaultOptions,
90
+ startOnLoad: false,
91
+ onSpeechStart: () => {
92
+ this.emitVoiceStart();
93
+ },
94
+ onSpeechEnd: (audio) => {
95
+ this.emitVoiceStop();
96
+ },
97
+ onVADMisfire: () => {
98
+ },
99
+ minSpeechMs: this.options?.minSpeechMs || 150,
100
+ positiveSpeechThreshold: 0.5,
101
+ negativeSpeechThreshold: 0.35,
102
+ redemptionMs: this.options?.minSilenceMs || 450,
103
+ preSpeechPadMs: 50,
104
+ processorType: "ScriptProcessor",
105
+ onnxWASMBasePath: "/ort/",
106
+ baseAssetPath: "/vad/",
107
+ workletOptions: {}
108
+ });
109
+ }
110
+ if (!this.vad.listening) {
111
+ await this.vad.start();
112
+ }
113
+ this.running = true;
114
+ } catch (error) {
115
+ this.running = false;
116
+ throw new Error(
117
+ error?.message || "Failed to initialize voice activity detector"
118
+ );
119
+ }
120
+ }
121
+ stop() {
122
+ if (!this.running || !this.vad) return;
123
+ try {
124
+ this.vad.pause();
125
+ this.running = false;
126
+ } catch (error) {
127
+ }
128
+ }
129
+ destroy() {
130
+ this.stop();
131
+ if (this.vad) {
132
+ try {
133
+ this.vad.destroy();
134
+ } catch (error) {
135
+ }
136
+ this.vad = null;
137
+ }
138
+ this.voiceStartListeners.clear();
139
+ this.voiceStopListeners.clear();
140
+ }
141
+ isActive() {
142
+ return this.running && this.vad !== null && this.vad.listening;
143
+ }
144
+ onVoiceStart(listener) {
145
+ this.voiceStartListeners.add(listener);
146
+ return () => this.voiceStartListeners.delete(listener);
147
+ }
148
+ onVoiceStop(listener) {
149
+ this.voiceStopListeners.add(listener);
150
+ return () => this.voiceStopListeners.delete(listener);
151
+ }
152
+ emitVoiceStart() {
153
+ this.voiceStartListeners.forEach((listener) => {
154
+ try {
155
+ listener();
156
+ } catch (error) {
157
+ console.error("Error in voice start listener:", error);
158
+ }
159
+ });
160
+ }
161
+ emitVoiceStop() {
162
+ this.voiceStopListeners.forEach((listener) => {
163
+ try {
164
+ listener();
165
+ } catch (error) {
166
+ console.error("Error in voice stop listener:", error);
167
+ }
168
+ });
169
+ }
170
+ };
171
+
172
+ // src/stt/stt-logic.ts
173
+ var ResetSTTLogic2 = class {
174
+ constructor(onLog, onTranscript, options = {}) {
175
+ this.isListening = false;
176
+ this.fullTranscript = "";
177
+ this.heardWords = [];
178
+ this.onWordsUpdate = null;
179
+ this.onMicTimeUpdate = null;
180
+ this.onRestartMetrics = null;
181
+ this.micOnTime = 0;
182
+ this.sessionDuration = 3e4;
183
+ this.lastTickTime = 0;
184
+ this.micTimeInterval = null;
185
+ this.restartCount = 0;
186
+ this.isRestarting = false;
187
+ this.isRecognitionRunning = false;
188
+ this.lastInterimTranscript = "";
189
+ this.lastInterimSaveTime = 0;
190
+ this.interimSaveInterval = 1e3;
191
+ this.lastInterimResultTime = 0;
192
+ this.lastSavedLength = 0;
193
+ this.transcriptBeforeRestart = "";
194
+ this.sessionStartTranscript = "";
195
+ this.sessionId = 0;
196
+ this.awaitingRestartFirstResultId = null;
197
+ this.lastWasFinal = false;
198
+ this.restartMetrics = {};
199
+ this.isAutoRestarting = false;
200
+ this.onLog = onLog;
201
+ this.onTranscript = onTranscript;
202
+ this.options = {
203
+ sessionDurationMs: options.sessionDurationMs ?? 3e4,
204
+ interimSaveIntervalMs: options.interimSaveIntervalMs ?? 5e3,
205
+ preserveTranscriptOnStart: options.preserveTranscriptOnStart ?? false
206
+ };
207
+ this.sessionDuration = this.options.sessionDurationMs;
208
+ this.interimSaveInterval = this.options.interimSaveIntervalMs;
209
+ const SpeechRecognitionAPI = window.SpeechRecognition || window.webkitSpeechRecognition;
210
+ if (!SpeechRecognitionAPI) {
211
+ this.onLog("Speech Recognition API not supported", "error");
212
+ throw new Error("Speech Recognition API not available");
213
+ }
214
+ this.recognition = new SpeechRecognitionAPI();
215
+ this.setupRecognition();
216
+ }
217
+ setWordsUpdateCallback(callback) {
218
+ this.onWordsUpdate = callback;
219
+ }
220
+ setMicTimeUpdateCallback(callback) {
221
+ this.onMicTimeUpdate = callback;
222
+ }
223
+ setRestartMetricsCallback(callback) {
224
+ this.onRestartMetrics = callback;
225
+ }
226
+ setVadCallbacks(onSpeechStart, onSpeechEnd) {
227
+ this.onUserSpeechStart = onSpeechStart || void 0;
228
+ this.onUserSpeechEnd = onSpeechEnd || void 0;
229
+ }
230
+ getSessionDurationMs() {
231
+ return this.sessionDuration;
232
+ }
233
+ isInAutoRestart() {
234
+ return this.isAutoRestarting;
235
+ }
236
+ getFullTranscript() {
237
+ if (this.transcriptBeforeRestart.length > 0) {
238
+ if (this.fullTranscript.length > 0) {
239
+ return (this.transcriptBeforeRestart + " " + this.fullTranscript).trim();
240
+ }
241
+ return this.transcriptBeforeRestart;
242
+ }
243
+ return this.fullTranscript;
244
+ }
245
+ clearTranscript() {
246
+ this.fullTranscript = "";
247
+ this.transcriptBeforeRestart = "";
248
+ this.sessionStartTranscript = "";
249
+ this.heardWords = [];
250
+ }
251
+ setupRecognition() {
252
+ this.recognition.lang = "en-US";
253
+ this.recognition.interimResults = true;
254
+ this.recognition.continuous = true;
255
+ this.recognition.maxAlternatives = 1;
256
+ this.resultHandler = (event) => {
257
+ const speechEvent = event;
258
+ let completeTranscript = "";
259
+ for (let i = 0; i < speechEvent.results.length; i++) {
260
+ completeTranscript += speechEvent.results[i][0].transcript + " ";
261
+ }
262
+ completeTranscript = completeTranscript.trim();
263
+ const isFinal = speechEvent.results[speechEvent.results.length - 1].isFinal;
264
+ completeTranscript = this.collapseRepeats(completeTranscript);
265
+ this.lastInterimTranscript = completeTranscript;
266
+ this.lastInterimResultTime = Date.now();
267
+ if (this.awaitingRestartFirstResultId != null) {
268
+ const rid = this.awaitingRestartFirstResultId;
269
+ if (this.restartMetrics[rid] && !this.restartMetrics[rid].firstResultAt) {
270
+ this.restartMetrics[rid].firstResultAt = Date.now();
271
+ const delta = this.restartMetrics[rid].firstResultAt - this.restartMetrics[rid].requestedAt;
272
+ this.onLog(
273
+ `\u{1F514} First result after restart #${rid} in ${delta}ms`,
274
+ "info"
275
+ );
276
+ this.awaitingRestartFirstResultId = null;
277
+ }
278
+ }
279
+ this.onLog(
280
+ `[${isFinal ? "FINAL" : "INTERIM"}] "${completeTranscript}"`,
281
+ isFinal ? "info" : "warning"
282
+ );
283
+ if (!isFinal && this.lastWasFinal) {
284
+ try {
285
+ this.onUserSpeechStart?.();
286
+ } catch {
287
+ }
288
+ }
289
+ this.lastWasFinal = isFinal;
290
+ if (isFinal) {
291
+ this.fullTranscript = (this.sessionStartTranscript + " " + completeTranscript).trim();
292
+ this.fullTranscript = this.collapseRepeats(this.fullTranscript);
293
+ this.heardWords = this.fullTranscript.split(/\s+/).filter((word) => word.length > 0);
294
+ this.onTranscript(this.getFullTranscript());
295
+ this.lastSavedLength = this.fullTranscript.length;
296
+ if (this.onWordsUpdate) this.onWordsUpdate(this.heardWords);
297
+ this.lastInterimTranscript = "";
298
+ if (this.awaitingRestartFirstResultId != null) {
299
+ const rid = this.awaitingRestartFirstResultId;
300
+ if (this.restartMetrics[rid] && !this.restartMetrics[rid].firstResultAt) {
301
+ this.restartMetrics[rid].firstResultAt = Date.now();
302
+ this.restartMetrics[rid].startedAt || this.restartMetrics[rid].startAttemptAt || Date.now();
303
+ const firstResultDelta = this.restartMetrics[rid].firstResultAt - this.restartMetrics[rid].requestedAt;
304
+ this.onLog(
305
+ `\u{1F514} First result after restart #${rid} in ${firstResultDelta}ms`,
306
+ "info"
307
+ );
308
+ this.awaitingRestartFirstResultId = null;
309
+ }
310
+ }
311
+ }
312
+ };
313
+ this.recognition.addEventListener("result", this.resultHandler);
314
+ this.errorHandler = (event) => {
315
+ const errorEvent = event;
316
+ if (errorEvent.error === "aborted" && this.isRestarting) {
317
+ this.onLog("Aborted during restart (ignored)", "info");
318
+ this.isRecognitionRunning = false;
319
+ return;
320
+ }
321
+ this.onLog(`Error: ${errorEvent.error}`, "error");
322
+ if (errorEvent.error === "no-speech" || errorEvent.error === "audio-capture" || errorEvent.error === "network") {
323
+ setTimeout(() => {
324
+ if (this.isListening && !this.isRestarting && !this.isRecognitionRunning) {
325
+ try {
326
+ this.recognition.start();
327
+ this.isRecognitionRunning = true;
328
+ this.sessionId++;
329
+ } catch (e) {
330
+ this.onLog(`Failed restart after error: ${e}`, "error");
331
+ }
332
+ }
333
+ }, 500);
334
+ } else {
335
+ this.onLog(
336
+ `Unhandled SpeechRecognition error: ${errorEvent.error}`,
337
+ "warning"
338
+ );
339
+ }
340
+ };
341
+ this.recognition.addEventListener("error", this.errorHandler);
342
+ this.endHandler = () => {
343
+ this.isRecognitionRunning = false;
344
+ if (this.isListening && !this.isRestarting) {
345
+ setTimeout(() => {
346
+ if (this.isListening && !this.isRestarting) {
347
+ try {
348
+ this.recognition.start();
349
+ this.isRecognitionRunning = true;
350
+ this.sessionId++;
351
+ this.onLog(
352
+ `\u{1F501} Auto-resumed recognition after end (session ${this.sessionId})`,
353
+ "info"
354
+ );
355
+ } catch (e) {
356
+ this.onLog(`Failed to auto-start after end: ${e}`, "error");
357
+ }
358
+ }
359
+ }, 100);
360
+ }
361
+ };
362
+ this.recognition.addEventListener("end", this.endHandler);
363
+ this.startHandler = () => {
364
+ this.isRecognitionRunning = true;
365
+ const rid = this.awaitingRestartFirstResultId;
366
+ if (rid != null && this.restartMetrics[rid]) {
367
+ if (!this.restartMetrics[rid].startedAt) {
368
+ this.restartMetrics[rid].startedAt = Date.now();
369
+ this.onLog(
370
+ `\u25B6\uFE0F Restart #${rid} recognition started in ${this.restartMetrics[rid].startedAt - this.restartMetrics[rid].requestedAt}ms`,
371
+ "info"
372
+ );
373
+ }
374
+ }
375
+ };
376
+ this.recognition.addEventListener("start", this.startHandler);
377
+ }
378
+ waitForEventOnce(eventName, timeoutMs) {
379
+ return new Promise((resolve) => {
380
+ let timer = null;
381
+ const handler = (ev) => {
382
+ if (timer !== null) clearTimeout(timer);
383
+ this.recognition.removeEventListener(eventName, handler);
384
+ resolve(ev);
385
+ };
386
+ this.recognition.addEventListener(eventName, handler);
387
+ timer = window.setTimeout(() => {
388
+ this.recognition.removeEventListener(eventName, handler);
389
+ resolve(null);
390
+ }, timeoutMs);
391
+ });
392
+ }
393
+ startMicTimer() {
394
+ this.lastTickTime = Date.now();
395
+ this.lastInterimSaveTime = Date.now();
396
+ this.micTimeInterval = window.setInterval(() => {
397
+ if (this.isListening) {
398
+ const now = Date.now();
399
+ const elapsed = now - this.lastTickTime;
400
+ this.micOnTime += elapsed;
401
+ this.lastTickTime = now;
402
+ if (now - this.lastInterimSaveTime >= this.interimSaveInterval) {
403
+ this.saveInterimToFinal();
404
+ this.lastInterimSaveTime = now;
405
+ }
406
+ if (this.micOnTime >= this.sessionDuration) {
407
+ if (!this.isRestarting) this.performRestart();
408
+ }
409
+ if (this.onMicTimeUpdate) this.onMicTimeUpdate(this.micOnTime);
410
+ }
411
+ }, 100);
412
+ }
413
+ stopMicTimer() {
414
+ if (this.micTimeInterval) {
415
+ clearInterval(this.micTimeInterval);
416
+ this.micTimeInterval = null;
417
+ }
418
+ }
419
+ saveInterimToFinal() {
420
+ if (!this.lastInterimTranscript) return;
421
+ const now = Date.now();
422
+ if (now - this.lastInterimResultTime > this.interimSaveInterval && this.lastInterimTranscript.length > this.lastSavedLength) {
423
+ this.fullTranscript = (this.fullTranscript + " " + this.lastInterimTranscript).trim();
424
+ this.fullTranscript = this.collapseRepeats(this.fullTranscript);
425
+ this.lastSavedLength = this.fullTranscript.length;
426
+ if (this.onWordsUpdate) {
427
+ const words = this.fullTranscript.split(/\s+/).filter((w) => w.length > 0);
428
+ this.onWordsUpdate(words);
429
+ }
430
+ this.onTranscript(this.getFullTranscript());
431
+ }
432
+ }
433
+ getSuffixToAppend(base, current) {
434
+ if (!base || base.length === 0) return current;
435
+ if (!current || current.length === 0) return "";
436
+ base = base.trim();
437
+ current = current.trim();
438
+ if (current.startsWith(base)) {
439
+ return current.slice(base.length).trim();
440
+ }
441
+ const maxOverlap = Math.min(base.length, current.length);
442
+ for (let overlap = maxOverlap; overlap > 0; overlap--) {
443
+ if (base.endsWith(current.slice(0, overlap))) {
444
+ return current.slice(overlap).trim();
445
+ }
446
+ }
447
+ return current;
448
+ }
449
+ collapseRepeats(text) {
450
+ if (!text || text.trim().length === 0) return text.trim();
451
+ let normalized = text.replace(/\s+/g, " ").trim();
452
+ const n = normalized.length;
453
+ const lps = new Array(n).fill(0);
454
+ for (let i = 1; i < n; i++) {
455
+ let j = lps[i - 1];
456
+ while (j > 0 && normalized[i] !== normalized[j]) j = lps[j - 1];
457
+ if (normalized[i] === normalized[j]) j++;
458
+ lps[i] = j;
459
+ }
460
+ const period = n - lps[n - 1];
461
+ if (period < n && n % period === 0) {
462
+ return normalized.slice(0, period).trim();
463
+ }
464
+ const words = normalized.split(" ");
465
+ for (let block = Math.min(20, Math.floor(words.length / 2)); block >= 1; block--) {
466
+ let i = 0;
467
+ while (i + 2 * block <= words.length) {
468
+ let blockA = words.slice(i, i + block).join(" ");
469
+ let blockB = words.slice(i + block, i + 2 * block).join(" ");
470
+ if (blockA === blockB) {
471
+ words.splice(i + block, block);
472
+ } else {
473
+ i++;
474
+ }
475
+ }
476
+ }
477
+ const collapsedWords = [];
478
+ for (const w of words) {
479
+ if (collapsedWords.length === 0 || collapsedWords[collapsedWords.length - 1] !== w)
480
+ collapsedWords.push(w);
481
+ }
482
+ return collapsedWords.join(" ").trim();
483
+ }
484
+ performRestart() {
485
+ if (!this.isListening || this.isRestarting) return;
486
+ const restartStartTime = Date.now();
487
+ this.restartCount++;
488
+ this.isRestarting = true;
489
+ this.isAutoRestarting = true;
490
+ const rid = ++this.sessionId;
491
+ this.awaitingRestartFirstResultId = rid;
492
+ this.restartMetrics[rid] = { requestedAt: restartStartTime };
493
+ this.onLog(
494
+ `\u{1F504} [AUTO-RESTART] Session ${rid} - buffering transcript, waiting for silence...`,
495
+ "warning"
496
+ );
497
+ if (this.lastInterimTranscript.trim().length > 0) {
498
+ this.saveInterimToFinal();
499
+ }
500
+ this.transcriptBeforeRestart = this.getFullTranscript();
501
+ this.fullTranscript = "";
502
+ this.sessionStartTranscript = "";
503
+ this.lastInterimTranscript = "";
504
+ this.heardWords = [];
505
+ this.stopMicTimer();
506
+ const stopTimeout = 600;
507
+ const startTimeout = 1e3;
508
+ const firstResultTimeout = 2e3;
509
+ const stopNow = async () => {
510
+ try {
511
+ if (this.isRecognitionRunning) {
512
+ this.recognition.stop();
513
+ } else {
514
+ this.onLog("Recognition not running at stop attempt", "warning");
515
+ }
516
+ } catch (err) {
517
+ this.onLog(`Stop threw: ${err}`, "warning");
518
+ }
519
+ const endEvent = await this.waitForEventOnce("end", stopTimeout);
520
+ if (!endEvent) {
521
+ try {
522
+ this.recognition.abort();
523
+ } catch (err) {
524
+ this.onLog(`Abort also failed: ${err}`, "error");
525
+ }
526
+ await this.waitForEventOnce("end", 300);
527
+ }
528
+ this.restartMetrics[rid].stopAt = Date.now();
529
+ };
530
+ (async () => {
531
+ await stopNow();
532
+ this.restartMetrics[rid].startAttemptAt = Date.now();
533
+ try {
534
+ if (!this.isRecognitionRunning) {
535
+ this.sessionId = rid;
536
+ this.recognition.start();
537
+ } else {
538
+ this.onLog(
539
+ "Recognition already running at restart time; skipping start.",
540
+ "warning"
541
+ );
542
+ }
543
+ } catch (e) {
544
+ this.onLog(`Failed to start recognition after restart: ${e}`, "error");
545
+ }
546
+ const startEv = await this.waitForEventOnce("start", startTimeout);
547
+ if (startEv) {
548
+ this.restartMetrics[rid].startedAt = Date.now();
549
+ } else {
550
+ this.onLog(
551
+ `Restart #${rid} did not produce start event within ${startTimeout}ms`,
552
+ "warning"
553
+ );
554
+ }
555
+ const resEv = await this.waitForEventOnce("result", firstResultTimeout);
556
+ if (resEv) {
557
+ if (this.restartMetrics[rid])
558
+ this.restartMetrics[rid].firstResultAt = Date.now();
559
+ const firstResultDelta = (this.restartMetrics[rid].firstResultAt || Date.now()) - (this.restartMetrics[rid].requestedAt || Date.now());
560
+ this.onLog(
561
+ `\u{1F514} First result after restart #${rid} in ${firstResultDelta}ms`,
562
+ "info"
563
+ );
564
+ } else {
565
+ this.onLog(
566
+ `Restart #${rid} produced no result within ${firstResultTimeout}ms`,
567
+ "warning"
568
+ );
569
+ }
570
+ const startedAt = this.restartMetrics[rid].startedAt || this.restartMetrics[rid].startAttemptAt || Date.now();
571
+ const restartDuration = startedAt - this.restartMetrics[rid].requestedAt;
572
+ if (this.onRestartMetrics)
573
+ this.onRestartMetrics(this.restartCount, restartDuration);
574
+ this.onLog(
575
+ `\u2705 Session ${rid} restarted in ${restartDuration}ms - resuming from silence gate`,
576
+ "info"
577
+ );
578
+ this.startMicTimer();
579
+ this.isRestarting = false;
580
+ this.isAutoRestarting = false;
581
+ })();
582
+ }
583
+ start() {
584
+ if (this.isListening) return;
585
+ try {
586
+ this.isListening = true;
587
+ if (!this.options.preserveTranscriptOnStart) {
588
+ this.fullTranscript = "";
589
+ this.heardWords = [];
590
+ this.transcriptBeforeRestart = "";
591
+ this.sessionStartTranscript = "";
592
+ } else {
593
+ this.sessionStartTranscript = this.fullTranscript;
594
+ }
595
+ this.micOnTime = 0;
596
+ this.restartCount = 0;
597
+ this.lastSavedLength = 0;
598
+ this.lastInterimTranscript = "";
599
+ this.lastWasFinal = false;
600
+ if (!this.isRecognitionRunning) {
601
+ this.sessionId++;
602
+ this.recognition.start();
603
+ this.isRecognitionRunning = true;
604
+ }
605
+ this.startMicTimer();
606
+ this.onLog(
607
+ "Listening started (auto-restart every 30s of mic time)",
608
+ "info"
609
+ );
610
+ } catch (error) {
611
+ this.isListening = false;
612
+ this.onLog(`Failed to start: ${error}`, "error");
613
+ }
614
+ }
615
+ stop() {
616
+ if (!this.isListening) return;
617
+ try {
618
+ this.isListening = false;
619
+ this.isAutoRestarting = false;
620
+ this.stopMicTimer();
621
+ this.recognition.stop();
622
+ this.isRecognitionRunning = false;
623
+ this.onLog(
624
+ `Stopped listening (total mic time: ${(this.micOnTime / 1e3).toFixed(
625
+ 1
626
+ )}s, restarts: ${this.restartCount})`,
627
+ "info"
628
+ );
629
+ } catch (error) {
630
+ this.onLog(`Failed to stop: ${error}`, "error");
631
+ }
632
+ }
633
+ destroy() {
634
+ this.isListening = false;
635
+ this.stopMicTimer();
636
+ try {
637
+ this.recognition.abort?.();
638
+ } catch (e) {
639
+ }
640
+ try {
641
+ if (this.resultHandler)
642
+ this.recognition.removeEventListener("result", this.resultHandler);
643
+ if (this.errorHandler)
644
+ this.recognition.removeEventListener("error", this.errorHandler);
645
+ if (this.endHandler)
646
+ this.recognition.removeEventListener(
647
+ "end",
648
+ this.endHandler
649
+ );
650
+ if (this.startHandler)
651
+ this.recognition.removeEventListener(
652
+ "start",
653
+ this.startHandler
654
+ );
655
+ } catch (e) {
656
+ }
657
+ }
658
+ };
659
+ var STTLogic = class extends ResetSTTLogic2 {
660
+ };
661
+
662
+ export { ResetSTTLogic, STTLogic, VADController };
663
+ //# sourceMappingURL=index.mjs.map
664
+ //# sourceMappingURL=index.mjs.map