@bendyline/squisq-editor-react 2.11.16 → 2.11.18

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.
Files changed (30) hide show
  1. package/dist/{chunk-ZDWZRVAY.js → chunk-2XXBDECJ.js} +1 -1
  2. package/dist/{chunk-DNB32SU2.js → chunk-45NQUR62.js} +1105 -130
  3. package/dist/{chunk-34FRFDQ6.js → chunk-CGENKHWY.js} +58 -102
  4. package/dist/chunk-DNEVUOBZ.js +14 -0
  5. package/dist/chunk-DNJ6HOES.js +93 -0
  6. package/dist/{chunk-EN5BYZOE.js → chunk-DPAMJV6A.js} +3 -2
  7. package/dist/{chunk-5TYCTE33.js → chunk-GIXXD2O3.js} +2189 -1834
  8. package/dist/{chunk-ENDRPV6T.js → chunk-H7YTL6SL.js} +1 -1
  9. package/dist/chunk-LRZJD5YD.js +107 -0
  10. package/dist/{chunk-4D7QMRTZ.js → chunk-U6NVT3BY.js} +466 -540
  11. package/dist/{chunk-WDQFWD7Z.js → chunk-UBFNLWCN.js} +2 -2
  12. package/dist/{chunk-27WOOJXR.js → chunk-WKZLBGPX.js} +1 -1
  13. package/dist/dictationSession-PN3OT5LJ.js +521 -0
  14. package/dist/index.d.ts +36 -21
  15. package/dist/index.js +54 -22
  16. package/dist/json-editor/index.js +3 -3
  17. package/dist/recorder/index.d.ts +2 -2
  18. package/dist/recorder/index.js +17 -13
  19. package/dist/{recorder-C6ArYQBa.d.ts → recorder-CRsw0eC7.d.ts} +6 -5
  20. package/dist/review/index.js +3 -2
  21. package/dist/shell/index.d.ts +2 -1
  22. package/dist/shell/index.js +10 -7
  23. package/dist/{shell-X0EoQbbN.d.ts → shell-Cb0P_ln8.d.ts} +170 -5
  24. package/dist/speech/index.d.ts +126 -0
  25. package/dist/speech/index.js +10 -0
  26. package/dist/styles/index.css +162 -29
  27. package/dist/teleprompter/index.d.ts +13 -5
  28. package/dist/teleprompter/index.js +3 -2
  29. package/dist/{useNarrationStage-DopDr_P5.d.ts → useNarrationStage-vKtk6H-V.d.ts} +7 -1
  30. package/package.json +10 -6
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  MarkdownEscape
3
- } from "./chunk-ZDWZRVAY.js";
3
+ } from "./chunk-2XXBDECJ.js";
4
4
  import {
5
5
  markdownToTiptap,
6
6
  tiptapToMarkdown
7
- } from "./chunk-EN5BYZOE.js";
7
+ } from "./chunk-DPAMJV6A.js";
8
8
 
9
9
  // src/jsonEditor/JsonEditorContext.tsx
10
10
  import { createContext, useContext, useEffect, useMemo } from "react";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RecorderModal
3
- } from "./chunk-4D7QMRTZ.js";
3
+ } from "./chunk-U6NVT3BY.js";
4
4
 
5
5
  // src/recorder/RecorderButton.tsx
6
6
  import { useCallback, useState } from "react";
@@ -0,0 +1,521 @@
1
+ import {
2
+ SPEECH_INPUT_SAMPLE_RATE
3
+ } from "./chunk-DNEVUOBZ.js";
4
+ import {
5
+ DEFAULT_RECORDER_DEVICE_SETTINGS,
6
+ buildRecorderAudioConstraints
7
+ } from "./chunk-LRZJD5YD.js";
8
+ import {
9
+ requestMicStream,
10
+ resolveFormat,
11
+ supportsMediaRecorder
12
+ } from "./chunk-DNJ6HOES.js";
13
+
14
+ // src/speech/microphoneSpeechActivity.ts
15
+ var SAMPLE_INTERVAL_MS = 50;
16
+ function createMicrophoneSpeechActivityMonitor(stream) {
17
+ if (typeof AudioContext === "undefined") return null;
18
+ return new WebAudioSpeechActivityMonitor(stream);
19
+ }
20
+ var WebAudioSpeechActivityMonitor = class {
21
+ constructor(stream) {
22
+ this.context = null;
23
+ this.source = null;
24
+ this.analyser = null;
25
+ this.timer = null;
26
+ this.samples = null;
27
+ this.gate = createSpeechGate();
28
+ this.stream = stream;
29
+ }
30
+ start(onActivity) {
31
+ if (this.timer !== null) return;
32
+ const context = new AudioContext();
33
+ this.context = context;
34
+ try {
35
+ const source = context.createMediaStreamSource(this.stream);
36
+ const analyser = context.createAnalyser();
37
+ analyser.fftSize = 1024;
38
+ analyser.smoothingTimeConstant = 0.15;
39
+ source.connect(analyser);
40
+ this.source = source;
41
+ this.analyser = analyser;
42
+ this.samples = new Float32Array(analyser.fftSize);
43
+ void context.resume().catch(() => void 0);
44
+ } catch (caught) {
45
+ this.stop();
46
+ throw caught;
47
+ }
48
+ this.timer = setInterval(() => {
49
+ if (!this.analyser || !this.samples) return;
50
+ this.analyser.getFloatTimeDomainData(this.samples);
51
+ const level = rootMeanSquare(this.samples);
52
+ onActivity(this.gate.step(level), level);
53
+ }, SAMPLE_INTERVAL_MS);
54
+ }
55
+ stop() {
56
+ if (this.timer !== null) {
57
+ clearInterval(this.timer);
58
+ this.timer = null;
59
+ }
60
+ this.source?.disconnect();
61
+ this.analyser?.disconnect();
62
+ this.source = null;
63
+ this.analyser = null;
64
+ this.samples = null;
65
+ const context = this.context;
66
+ this.context = null;
67
+ if (context) void context.close().catch(() => void 0);
68
+ }
69
+ };
70
+ function createSpeechGate() {
71
+ let noiseFloor = 4e-3;
72
+ let speaking = false;
73
+ return {
74
+ step(level) {
75
+ const openThreshold = Math.max(0.012, noiseFloor * 2.8);
76
+ const closeThreshold = Math.max(8e-3, noiseFloor * 1.8);
77
+ speaking = level >= (speaking ? closeThreshold : openThreshold);
78
+ if (!speaking) {
79
+ noiseFloor = Math.min(0.03, noiseFloor * 0.95 + level * 0.05);
80
+ }
81
+ return speaking;
82
+ }
83
+ };
84
+ }
85
+ function rootMeanSquare(samples) {
86
+ if (samples.length === 0) return 0;
87
+ let sum = 0;
88
+ for (const sample of samples) sum += sample * sample;
89
+ return Math.sqrt(sum / samples.length);
90
+ }
91
+
92
+ // src/speech/progressiveSpeechToText.ts
93
+ var DEFAULT_SEGMENT_MS = 2500;
94
+ var DEFAULT_SPEECH_PAUSE_MS = 350;
95
+ var DEFAULT_MIN_SEGMENT_MS = 650;
96
+ var DEFAULT_LONG_PAUSE_MS = 1e4;
97
+ var MAX_RECOGNITION_PROMPT_CHARS = 1e3;
98
+ var ProgressiveSpeechToText = class {
99
+ constructor(options) {
100
+ this.abortController = new AbortController();
101
+ this.activityMonitorStarted = false;
102
+ this.recorder = null;
103
+ this.chunks = [];
104
+ this.segmentTimer = null;
105
+ this.transcriptionQueue = Promise.resolve();
106
+ this.pending = 0;
107
+ this.stopPromise = null;
108
+ this.resolveStop = null;
109
+ this.finalSegmentRequested = false;
110
+ this.segmentStartedAt = 0;
111
+ this.segmentHasSpeech = false;
112
+ this.currentlySpeaking = false;
113
+ this.lastSpeechAt = null;
114
+ this.confirmedSilenceMs = 0;
115
+ this.recognitionPrompt = "";
116
+ this.hasTranscript = false;
117
+ this.longPauseNotified = false;
118
+ this.disposed = false;
119
+ this.stream = options.stream;
120
+ this.mimeType = options.mimeType ?? "";
121
+ this.segmentMs = options.segmentMs ?? DEFAULT_SEGMENT_MS;
122
+ this.speechPauseMs = options.speechPauseMs ?? DEFAULT_SPEECH_PAUSE_MS;
123
+ this.minSegmentMs = options.minSegmentMs ?? DEFAULT_MIN_SEGMENT_MS;
124
+ this.longPauseMs = options.longPauseMs ?? DEFAULT_LONG_PAUSE_MS;
125
+ this.activityMonitor = options.activityMonitor === void 0 ? createMicrophoneSpeechActivityMonitor(options.stream) : options.activityMonitor;
126
+ this.transcribe = options.transcribe;
127
+ this.onTranscript = options.onTranscript;
128
+ this.onError = options.onError;
129
+ this.onLongPause = options.onLongPause;
130
+ this.onAudioLevel = options.onAudioLevel;
131
+ this.onPendingChange = options.onPendingChange;
132
+ }
133
+ start() {
134
+ if (this.disposed || this.recorder) return;
135
+ this.startSegment();
136
+ this.startActivityMonitor();
137
+ }
138
+ /** Stop capture, release the microphone, and wait for queued STT work. */
139
+ stop() {
140
+ if (this.stopPromise) return this.stopPromise;
141
+ this.finalSegmentRequested = true;
142
+ this.stopActivityMonitor();
143
+ this.clearSegmentTimer();
144
+ this.stopPromise = new Promise((resolve) => {
145
+ this.resolveStop = resolve;
146
+ });
147
+ const recorder = this.recorder;
148
+ if (recorder?.state === "recording") {
149
+ recorder.stop();
150
+ } else if (!recorder) {
151
+ void this.finish();
152
+ }
153
+ return this.stopPromise;
154
+ }
155
+ /** Tear down without delivering any more transcription callbacks. */
156
+ cancel() {
157
+ if (this.disposed) return;
158
+ this.disposed = true;
159
+ this.abortController.abort();
160
+ this.stopActivityMonitor();
161
+ this.clearSegmentTimer();
162
+ const recorder = this.recorder;
163
+ this.recorder = null;
164
+ if (recorder) {
165
+ recorder.ondataavailable = null;
166
+ recorder.onstop = null;
167
+ recorder.onerror = null;
168
+ if (recorder.state !== "inactive") {
169
+ try {
170
+ recorder.stop();
171
+ } catch {
172
+ }
173
+ }
174
+ }
175
+ this.releaseStream();
176
+ this.resolveStop?.();
177
+ this.resolveStop = null;
178
+ }
179
+ startSegment() {
180
+ if (this.disposed || this.finalSegmentRequested) return;
181
+ const recorderOptions = this.mimeType ? { mimeType: this.mimeType } : void 0;
182
+ const recorder = new MediaRecorder(this.stream, recorderOptions);
183
+ this.recorder = recorder;
184
+ this.chunks = [];
185
+ this.segmentStartedAt = Date.now();
186
+ this.segmentHasSpeech = this.currentlySpeaking;
187
+ recorder.ondataavailable = (event) => {
188
+ if (!this.disposed && event.data.size > 0) this.chunks.push(event.data);
189
+ };
190
+ recorder.onerror = (event) => {
191
+ const error = event.error;
192
+ this.onError(error ?? new Error("Microphone recording failed."));
193
+ this.finalSegmentRequested = true;
194
+ this.clearSegmentTimer();
195
+ };
196
+ recorder.onstop = () => {
197
+ if (this.disposed || this.recorder !== recorder) return;
198
+ this.recorder = null;
199
+ const type = recorder.mimeType || this.mimeType || "audio/webm";
200
+ const blob = new Blob(this.chunks, { type });
201
+ const capturedMs = Math.max(0, Date.now() - this.segmentStartedAt);
202
+ this.chunks = [];
203
+ if (!this.finalSegmentRequested) this.startSegment();
204
+ if (blob.size > 0) this.enqueueTranscription(blob, type, capturedMs);
205
+ if (this.finalSegmentRequested) void this.finish();
206
+ };
207
+ recorder.start();
208
+ this.segmentTimer = setTimeout(() => {
209
+ this.segmentTimer = null;
210
+ if (!this.disposed && !this.finalSegmentRequested && recorder.state === "recording") {
211
+ recorder.stop();
212
+ }
213
+ }, this.segmentMs);
214
+ }
215
+ startActivityMonitor() {
216
+ if (!this.activityMonitor || this.activityMonitorStarted) return;
217
+ try {
218
+ this.activityMonitor.start((speaking, level) => {
219
+ this.onAudioLevel?.(level);
220
+ this.onSpeechActivity(speaking);
221
+ });
222
+ this.activityMonitorStarted = true;
223
+ } catch {
224
+ this.activityMonitor.stop();
225
+ this.activityMonitor = null;
226
+ }
227
+ }
228
+ stopActivityMonitor() {
229
+ this.activityMonitor?.stop();
230
+ }
231
+ onSpeechActivity(speaking) {
232
+ if (this.disposed || this.finalSegmentRequested) return;
233
+ const now = Date.now();
234
+ this.currentlySpeaking = speaking;
235
+ if (speaking) {
236
+ this.segmentHasSpeech = true;
237
+ this.lastSpeechAt = now;
238
+ this.longPauseNotified = false;
239
+ return;
240
+ }
241
+ if (this.lastSpeechAt === null) return;
242
+ const silenceMs = now - this.lastSpeechAt;
243
+ if (this.segmentHasSpeech && now - this.segmentStartedAt >= this.minSegmentMs && silenceMs >= this.speechPauseMs) {
244
+ this.flushCurrentSegment();
245
+ }
246
+ }
247
+ flushCurrentSegment() {
248
+ const recorder = this.recorder;
249
+ if (!recorder || recorder.state !== "recording") return;
250
+ this.clearSegmentTimer();
251
+ recorder.stop();
252
+ }
253
+ setPending(next) {
254
+ this.pending = next;
255
+ if (!this.disposed) this.onPendingChange?.(next);
256
+ }
257
+ enqueueTranscription(blob, mimeType, capturedMs) {
258
+ this.setPending(this.pending + 1);
259
+ this.transcriptionQueue = this.transcriptionQueue.then(async () => {
260
+ if (this.disposed) return;
261
+ try {
262
+ const text = normalizeSpeechTranscript(
263
+ await this.transcribe(
264
+ blob,
265
+ mimeType,
266
+ this.abortController.signal,
267
+ this.recognitionPrompt
268
+ )
269
+ );
270
+ if (this.disposed) return;
271
+ if (text) {
272
+ this.confirmedSilenceMs = 0;
273
+ this.hasTranscript = true;
274
+ this.longPauseNotified = false;
275
+ this.recognitionPrompt = extendRecognitionPrompt(this.recognitionPrompt, text);
276
+ this.onTranscript(text);
277
+ return;
278
+ }
279
+ this.confirmedSilenceMs += capturedMs;
280
+ if (!this.longPauseNotified && this.onLongPause && this.confirmedSilenceMs >= this.longPauseMs) {
281
+ this.longPauseNotified = true;
282
+ this.onLongPause(this.hasTranscript);
283
+ }
284
+ } catch (caught) {
285
+ if (this.disposed || this.abortController.signal.aborted) return;
286
+ this.onError(caught instanceof Error ? caught : new Error(String(caught)));
287
+ } finally {
288
+ this.setPending(Math.max(0, this.pending - 1));
289
+ }
290
+ });
291
+ }
292
+ async finish() {
293
+ this.clearSegmentTimer();
294
+ this.stopActivityMonitor();
295
+ this.releaseStream();
296
+ await this.transcriptionQueue;
297
+ if (this.disposed) return;
298
+ this.disposed = true;
299
+ this.resolveStop?.();
300
+ this.resolveStop = null;
301
+ }
302
+ clearSegmentTimer() {
303
+ if (this.segmentTimer === null) return;
304
+ clearTimeout(this.segmentTimer);
305
+ this.segmentTimer = null;
306
+ }
307
+ releaseStream() {
308
+ for (const track of this.stream.getTracks()) track.stop();
309
+ }
310
+ };
311
+ function normalizeSpeechTranscript(value) {
312
+ const text = value.trim();
313
+ return /^\[\s*BLANK_AUDIO\s*\]$/i.test(text) ? "" : text;
314
+ }
315
+ function extendRecognitionPrompt(previous, transcript) {
316
+ const combined = `${previous.trim()} ${transcript.trim()}`.trim();
317
+ if (combined.length <= MAX_RECOGNITION_PROMPT_CHARS) return combined;
318
+ const tail = combined.slice(-MAX_RECOGNITION_PROMPT_CHARS);
319
+ const firstWhitespace = tail.search(/\s/);
320
+ return (firstWhitespace >= 0 ? tail.slice(firstWhitespace + 1) : tail).trim();
321
+ }
322
+
323
+ // src/speech/speechAudioWav.ts
324
+ async function microphoneTakeAsWav(blob) {
325
+ const encoded = await blobBytes(blob);
326
+ let audio;
327
+ try {
328
+ audio = await decodeAt16k(encoded);
329
+ } catch (caught) {
330
+ const detail = caught instanceof Error && caught.message ? ` (${caught.message})` : "";
331
+ throw new Error(`This recording could not be prepared for speech recognition${detail}`);
332
+ }
333
+ const channels = Array.from(
334
+ { length: audio.numberOfChannels },
335
+ (_, index) => audio.getChannelData(index)
336
+ );
337
+ const mono = downmixToMono(channels);
338
+ const samples = audio.sampleRate === SPEECH_INPUT_SAMPLE_RATE ? mono : resampleLinear(mono, audio.sampleRate, SPEECH_INPUT_SAMPLE_RATE);
339
+ return encodeMonoPcm16Wav([samples], SPEECH_INPUT_SAMPLE_RATE).buffer;
340
+ }
341
+ function blobBytes(blob) {
342
+ if (typeof blob.arrayBuffer === "function") return blob.arrayBuffer();
343
+ return new Promise((resolve, reject) => {
344
+ const reader = new FileReader();
345
+ reader.onload = () => {
346
+ const result = reader.result;
347
+ if (result instanceof ArrayBuffer) resolve(result);
348
+ else reject(new Error("The recording could not be read."));
349
+ };
350
+ reader.onerror = () => reject(reader.error ?? new Error("The recording could not be read."));
351
+ reader.readAsArrayBuffer(blob);
352
+ });
353
+ }
354
+ async function decodeAt16k(encoded) {
355
+ if (typeof OfflineAudioContext !== "undefined") {
356
+ let offline = null;
357
+ try {
358
+ offline = new OfflineAudioContext(1, 1, SPEECH_INPUT_SAMPLE_RATE);
359
+ } catch {
360
+ offline = null;
361
+ }
362
+ if (offline) return offline.decodeAudioData(encoded);
363
+ }
364
+ if (typeof AudioContext === "undefined") {
365
+ throw new Error("Web Audio is unavailable");
366
+ }
367
+ const context = new AudioContext();
368
+ try {
369
+ return await context.decodeAudioData(encoded);
370
+ } finally {
371
+ await context.close().catch(() => void 0);
372
+ }
373
+ }
374
+ function downmixToMono(channels) {
375
+ const sampleCount = channels[0]?.length ?? 0;
376
+ if (channels.length === 0 || sampleCount === 0) {
377
+ throw new Error("The microphone recording was empty.");
378
+ }
379
+ if (channels.length === 1) return channels[0];
380
+ if (channels.some((channel) => channel.length !== sampleCount)) {
381
+ throw new Error("The microphone recording channels have different lengths.");
382
+ }
383
+ const mono = new Float32Array(sampleCount);
384
+ for (let index = 0; index < sampleCount; index += 1) {
385
+ let sum = 0;
386
+ for (const channel of channels) sum += channel[index] ?? 0;
387
+ mono[index] = sum / channels.length;
388
+ }
389
+ return mono;
390
+ }
391
+ function resampleLinear(samples, fromRate, toRate) {
392
+ if (!Number.isFinite(fromRate) || fromRate <= 0 || !Number.isFinite(toRate) || toRate <= 0) {
393
+ throw new Error("The microphone recording has an invalid sample rate.");
394
+ }
395
+ if (fromRate === toRate) return samples;
396
+ const outputLength = Math.max(1, Math.round(samples.length * toRate / fromRate));
397
+ const output = new Float32Array(outputLength);
398
+ const step = fromRate / toRate;
399
+ for (let index = 0; index < outputLength; index += 1) {
400
+ const position = index * step;
401
+ const left = Math.floor(position);
402
+ const right = Math.min(left + 1, samples.length - 1);
403
+ const fraction = position - left;
404
+ const a = samples[Math.min(left, samples.length - 1)] ?? 0;
405
+ const b = samples[right] ?? 0;
406
+ output[index] = a + (b - a) * fraction;
407
+ }
408
+ return output;
409
+ }
410
+ function encodeMonoPcm16Wav(channels, sampleRate) {
411
+ const sampleCount = channels[0]?.length ?? 0;
412
+ if (channels.length === 0 || sampleCount === 0) {
413
+ throw new Error("The microphone recording was empty.");
414
+ }
415
+ if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
416
+ throw new Error("The microphone recording has an invalid sample rate.");
417
+ }
418
+ if (channels.some((channel) => channel.length !== sampleCount)) {
419
+ throw new Error("The microphone recording channels have different lengths.");
420
+ }
421
+ const rate = Math.round(sampleRate);
422
+ const bytesPerSample = 2;
423
+ const dataBytes = sampleCount * bytesPerSample;
424
+ const buffer = new ArrayBuffer(44 + dataBytes);
425
+ const view = new DataView(buffer);
426
+ writeAscii(view, 0, "RIFF");
427
+ view.setUint32(4, 36 + dataBytes, true);
428
+ writeAscii(view, 8, "WAVE");
429
+ writeAscii(view, 12, "fmt ");
430
+ view.setUint32(16, 16, true);
431
+ view.setUint16(20, 1, true);
432
+ view.setUint16(22, 1, true);
433
+ view.setUint32(24, rate, true);
434
+ view.setUint32(28, rate * bytesPerSample, true);
435
+ view.setUint16(32, bytesPerSample, true);
436
+ view.setUint16(34, 16, true);
437
+ writeAscii(view, 36, "data");
438
+ view.setUint32(40, dataBytes, true);
439
+ for (let index = 0; index < sampleCount; index += 1) {
440
+ let mono = 0;
441
+ for (const channel of channels) mono += channel[index] ?? 0;
442
+ mono = Math.max(-1, Math.min(1, mono / channels.length));
443
+ const pcm = mono < 0 ? Math.round(mono * 32768) : Math.round(mono * 32767);
444
+ view.setInt16(44 + index * bytesPerSample, pcm, true);
445
+ }
446
+ return new Uint8Array(buffer);
447
+ }
448
+ function writeAscii(view, offset, value) {
449
+ for (let index = 0; index < value.length; index += 1) {
450
+ view.setUint8(offset + index, value.charCodeAt(index));
451
+ }
452
+ }
453
+
454
+ // src/speech/dictationSession.ts
455
+ function dictationAudioConstraints(settings = DEFAULT_RECORDER_DEVICE_SETTINGS) {
456
+ return {
457
+ ...buildRecorderAudioConstraints(settings),
458
+ echoCancellation: true,
459
+ noiseSuppression: true,
460
+ autoGainControl: true
461
+ };
462
+ }
463
+ function abortError() {
464
+ const error = new Error("Dictation was cancelled.");
465
+ error.name = "AbortError";
466
+ return error;
467
+ }
468
+ async function startDictationSession(options) {
469
+ const { provider, signal } = options;
470
+ if (!supportsMediaRecorder()) {
471
+ throw new Error("MediaRecorder is not defined in this environment.");
472
+ }
473
+ const ready = provider.prepare ? Promise.resolve().then(() => provider.prepare?.(signal)) : Promise.resolve();
474
+ ready.catch(() => void 0);
475
+ const stream = await requestMicStream(dictationAudioConstraints(options.deviceSettings));
476
+ if (signal.aborted) {
477
+ for (const track of stream.getTracks()) track.stop();
478
+ throw abortError();
479
+ }
480
+ const format = resolveFormat("audio");
481
+ const engine = new ProgressiveSpeechToText({
482
+ stream,
483
+ ...format.mimeType ? { mimeType: format.mimeType } : {},
484
+ transcribe: async (blob, _mimeType, takeSignal, prompt) => {
485
+ await ready;
486
+ const wav = await microphoneTakeAsWav(blob);
487
+ const result = await provider.transcribe(wav, {
488
+ signal: takeSignal,
489
+ ...prompt ? { prompt } : {}
490
+ });
491
+ return typeof result?.text === "string" ? result.text : "";
492
+ },
493
+ onTranscript: options.onTranscript,
494
+ onAudioLevel: options.onAudioLevel,
495
+ onPendingChange: options.onPendingChange,
496
+ onLongPause: options.onLongPause,
497
+ onError: (error) => {
498
+ engine.cancel();
499
+ options.onError(error);
500
+ }
501
+ });
502
+ ready.catch((caught) => {
503
+ if (signal.aborted) return;
504
+ engine.cancel();
505
+ options.onError(caught instanceof Error ? caught : new Error(String(caught)));
506
+ });
507
+ const onAbort = () => engine.cancel();
508
+ signal.addEventListener("abort", onAbort, { once: true });
509
+ engine.start();
510
+ return {
511
+ stop: () => engine.stop(),
512
+ cancel: () => {
513
+ signal.removeEventListener("abort", onAbort);
514
+ engine.cancel();
515
+ }
516
+ };
517
+ }
518
+ export {
519
+ dictationAudioConstraints,
520
+ startDictationSession
521
+ };