@epicbagel/saas-kit 1.11.1 → 1.12.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.
- package/README.md +19 -4
- package/dist/{chunk-ZZ7ICQR2.cjs → chunk-7Q4EFCUI.cjs} +93 -28
- package/dist/{chunk-EDEN7TAL.js → chunk-JKVVTFAP.js} +5 -4
- package/dist/{chunk-D47AMKT4.cjs → chunk-QWUMIAYD.cjs} +6 -5
- package/dist/{chunk-HLAHOVEI.js → chunk-RPSRCB5O.js} +79 -14
- package/dist/hooks/use-dictation.cjs +2 -2
- package/dist/hooks/use-dictation.d.cts +27 -5
- package/dist/hooks/use-dictation.d.ts +27 -5
- package/dist/hooks/use-dictation.js +1 -1
- package/dist/primitives/chat.cjs +14 -10
- package/dist/primitives/chat.d.cts +13 -6
- package/dist/primitives/chat.d.ts +13 -6
- package/dist/primitives/chat.js +11 -7
- package/dist/primitives/dictation-provider.cjs +2 -2
- package/dist/primitives/dictation-provider.d.cts +1 -1
- package/dist/primitives/dictation-provider.d.ts +1 -1
- package/dist/primitives/dictation-provider.js +1 -1
- package/dist/primitives/voice-dictation-button.cjs +3 -3
- package/dist/primitives/voice-dictation-button.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,8 +50,7 @@ For hidden/programmatic triggers, it uses the focused visible button as the
|
|
|
50
50
|
desktop anchor. Hosts can pass an explicit `anchorRef` for a custom trigger.
|
|
51
51
|
Without a visible anchor it opens full-screen. Nested and sibling dialogs keep
|
|
52
52
|
their focus and cancellation behavior.
|
|
53
|
-
The recorder runs until **Done**,
|
|
54
|
-
**Transcribing…**, then commits the returned text once. Failed requests keep the
|
|
53
|
+
The recorder runs until **Done**, finishes the last words, then commits the returned text once. Failed requests keep the
|
|
55
54
|
take available for retry. Cancel stops microphone tracks, aborts the request and
|
|
56
55
|
ignores a late answer. The kit never persists audio or knows provider credentials.
|
|
57
56
|
|
|
@@ -67,6 +66,21 @@ import { DictationProvider } from "@epicbagel/saas-kit/primitives/dictation-prov
|
|
|
67
66
|
</DictationProvider>
|
|
68
67
|
```
|
|
69
68
|
|
|
69
|
+
For words that appear while speaking, supply `stream` as well (or on its own).
|
|
70
|
+
The host connects the provided `MediaStream` to its speech service; the kit
|
|
71
|
+
captures the microphone only once. `onTranscript` replaces the live preview
|
|
72
|
+
with `{ transcript, interimTranscript }`. The adapter resolves when ready and
|
|
73
|
+
returns `{ finish, cancel }`: `finish()` drains the final audio and returns the
|
|
74
|
+
complete transcript; `cancel()` releases the transport without saving. The
|
|
75
|
+
adapter must honor `signal` during startup, streaming and finalization, and
|
|
76
|
+
report unexpected disconnection through `onError`.
|
|
77
|
+
|
|
78
|
+
Preview updates never invoke a control's commit callback. Done waits for
|
|
79
|
+
`finish()` before saving. If streaming fails and `transcribe` is also supplied,
|
|
80
|
+
the recorder shows a warning and uses its complete recording on Done. Retry
|
|
81
|
+
uses that same recording. With no batch adapter, an interrupted stream is an
|
|
82
|
+
error; partial words are never presented as a complete note.
|
|
83
|
+
|
|
70
84
|
Set `scopeKey` from the acting user and workspace (including impersonation, if
|
|
71
85
|
the app supports it). A changed scope discards the active take, stops recording
|
|
72
86
|
and aborts transcription without remounting the application. Responses from the
|
|
@@ -83,8 +97,9 @@ native recognition has no audio-level source.
|
|
|
83
97
|
failure. Choose either that result or `onTranscript` to commit; using both would
|
|
84
98
|
duplicate the host action. The callback receives one complete API transcript,
|
|
85
99
|
or finalized chunks with native recognition. API recording requires HTTPS (or
|
|
86
|
-
localhost), microphone permission and MediaRecorder.
|
|
87
|
-
|
|
100
|
+
localhost), microphone permission and MediaRecorder. Live interim text requires
|
|
101
|
+
the host's `stream` adapter; the batch `transcribe` adapter alone returns text
|
|
102
|
+
after Done. Live voice conversation controls remain a separate feature.
|
|
88
103
|
|
|
89
104
|
Without a provider, controls retain browser-native speech recognition. The
|
|
90
105
|
private ui-kit's `VoiceInput` uses this same provider and capture hook, and its
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// src/hooks/use-dictation.ts
|
|
4
4
|
var _react = require('react');
|
|
5
5
|
var DictationContext = _react.createContext.call(void 0, null);
|
|
6
|
-
function DictationProvider({ transcribe, scopeKey, children }) {
|
|
6
|
+
function DictationProvider({ transcribe, stream, scopeKey, children }) {
|
|
7
7
|
const scope = _react.useRef.call(void 0, { key: scopeKey, generation: 0 });
|
|
8
8
|
if (scope.current.key !== scopeKey) scope.current = { key: scopeKey, generation: scope.current.generation + 1 };
|
|
9
9
|
const generation = scope.current.generation;
|
|
10
|
-
const value = _react.useMemo.call(void 0, () => ({ transcribe, scope, generation }), [transcribe, generation]);
|
|
10
|
+
const value = _react.useMemo.call(void 0, () => ({ transcribe, stream, scope, generation }), [transcribe, stream, generation]);
|
|
11
11
|
return _react.createElement.call(void 0, DictationContext.Provider, { value }, children);
|
|
12
12
|
}
|
|
13
13
|
var EMPTY = { state: "idle", transcript: "", interimTranscript: "", error: null, canRetry: false };
|
|
@@ -128,6 +128,8 @@ function releaseTracks(session) {
|
|
|
128
128
|
}
|
|
129
129
|
function dispose(session) {
|
|
130
130
|
session.controller.abort();
|
|
131
|
+
_optionalChain([session, 'access', _18 => _18.streamController, 'optionalAccess', _19 => _19.abort, 'call', _20 => _20()]);
|
|
132
|
+
_optionalChain([session, 'access', _21 => _21.live, 'optionalAccess', _22 => _22.cancel, 'call', _23 => _23()]);
|
|
131
133
|
if (session.nativeTimer) clearTimeout(session.nativeTimer);
|
|
132
134
|
const recorder = session.recorder;
|
|
133
135
|
if (recorder) {
|
|
@@ -157,9 +159,10 @@ function dispose(session) {
|
|
|
157
159
|
}
|
|
158
160
|
function useDictation({ lang = "en-US", continuous = true, onTranscript } = {}) {
|
|
159
161
|
const provider = _react.useContext.call(void 0, DictationContext);
|
|
160
|
-
const transcribe = _nullishCoalesce(_optionalChain([provider, 'optionalAccess',
|
|
161
|
-
const
|
|
162
|
-
const
|
|
162
|
+
const transcribe = _nullishCoalesce(_optionalChain([provider, 'optionalAccess', _24 => _24.transcribe]), () => ( null));
|
|
163
|
+
const startStream = _nullishCoalesce(_optionalChain([provider, 'optionalAccess', _25 => _25.stream]), () => ( null));
|
|
164
|
+
const scope = _nullishCoalesce(_optionalChain([provider, 'optionalAccess', _26 => _26.scope]), () => ( null));
|
|
165
|
+
const scopeGeneration = _nullishCoalesce(_optionalChain([provider, 'optionalAccess', _27 => _27.generation]), () => ( 0));
|
|
163
166
|
const [snapshot, setSnapshot] = _react.useState.call(void 0, EMPTY);
|
|
164
167
|
const [supported, setSupported] = _react.useState.call(void 0, false);
|
|
165
168
|
const current = _react.useRef.call(void 0, null);
|
|
@@ -169,8 +172,8 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
169
172
|
activeScope.current = scope;
|
|
170
173
|
callback.current = onTranscript;
|
|
171
174
|
_react.useEffect.call(void 0, () => {
|
|
172
|
-
setSupported(transcribe ? typeof navigator !== "undefined" && !!_optionalChain([navigator, 'access',
|
|
173
|
-
}, [transcribe]);
|
|
175
|
+
setSupported(transcribe || startStream ? typeof navigator !== "undefined" && !!_optionalChain([navigator, 'access', _28 => _28.mediaDevices, 'optionalAccess', _29 => _29.getUserMedia]) && typeof MediaRecorder !== "undefined" : typeof recognitionConstructor() === "function");
|
|
176
|
+
}, [transcribe, startStream]);
|
|
174
177
|
const cancel = _react.useCallback.call(void 0, () => {
|
|
175
178
|
const session = current.current;
|
|
176
179
|
current.current = null;
|
|
@@ -190,9 +193,9 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
190
193
|
if (session) dispose(session);
|
|
191
194
|
};
|
|
192
195
|
}, []);
|
|
193
|
-
const isCurrent = _react.useCallback.call(void 0, (session) => mounted.current && current.current === session && session.scope === activeScope.current && session.scopeGeneration === (_nullishCoalesce(_optionalChain([session, 'access',
|
|
196
|
+
const isCurrent = _react.useCallback.call(void 0, (session) => mounted.current && current.current === session && session.scope === activeScope.current && session.scopeGeneration === (_nullishCoalesce(_optionalChain([session, 'access', _30 => _30.scope, 'optionalAccess', _31 => _31.current, 'access', _32 => _32.generation]), () => ( 0))) && !session.controller.signal.aborted, []);
|
|
194
197
|
const start = _react.useCallback.call(void 0, async () => {
|
|
195
|
-
if (scope !== activeScope.current || scopeGeneration !== (_nullishCoalesce(_optionalChain([scope, 'optionalAccess',
|
|
198
|
+
if (scope !== activeScope.current || scopeGeneration !== (_nullishCoalesce(_optionalChain([scope, 'optionalAccess', _33 => _33.current, 'access', _34 => _34.generation]), () => ( 0)))) return;
|
|
196
199
|
if (current.current && isCurrent(current.current) && ["requesting", "recording", "transcribing"].includes(current.current.phase)) return;
|
|
197
200
|
cancel();
|
|
198
201
|
let resolveCapture;
|
|
@@ -200,9 +203,12 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
200
203
|
const session = {
|
|
201
204
|
language: lang,
|
|
202
205
|
transcribe,
|
|
206
|
+
startStream,
|
|
207
|
+
liveFailed: false,
|
|
203
208
|
scope,
|
|
204
209
|
scopeGeneration,
|
|
205
210
|
controller: new AbortController(),
|
|
211
|
+
streamController: startStream ? new AbortController() : void 0,
|
|
206
212
|
phase: "requesting",
|
|
207
213
|
chunks: [],
|
|
208
214
|
capture: new Promise((resolve) => {
|
|
@@ -217,20 +223,20 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
217
223
|
transcript: "",
|
|
218
224
|
interim: "",
|
|
219
225
|
notified: /* @__PURE__ */ new Set(),
|
|
220
|
-
audioLevels: transcribe ? createAudioLevels() : void 0
|
|
226
|
+
audioLevels: transcribe || startStream ? createAudioLevels() : void 0
|
|
221
227
|
};
|
|
222
228
|
current.current = session;
|
|
223
229
|
setSnapshot({ ...EMPTY, state: "requesting" });
|
|
224
|
-
if (transcribe) {
|
|
230
|
+
if (transcribe || startStream) {
|
|
225
231
|
try {
|
|
226
|
-
if (!_optionalChain([navigator, 'access',
|
|
232
|
+
if (!_optionalChain([navigator, 'access', _35 => _35.mediaDevices, 'optionalAccess', _36 => _36.getUserMedia]) || typeof MediaRecorder === "undefined") throw new Error("unsupported");
|
|
227
233
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
228
234
|
if (!isCurrent(session)) {
|
|
229
235
|
stream.getTracks().forEach((track) => track.stop());
|
|
230
236
|
return;
|
|
231
237
|
}
|
|
232
238
|
session.stream = stream;
|
|
233
|
-
const mimeType = MIME_TYPES.find((mime) => _optionalChain([MediaRecorder, 'access',
|
|
239
|
+
const mimeType = MIME_TYPES.find((mime) => _optionalChain([MediaRecorder, 'access', _37 => _37.isTypeSupported, 'optionalCall', _38 => _38(mime)]));
|
|
234
240
|
const recorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream);
|
|
235
241
|
session.recorder = recorder;
|
|
236
242
|
recorder.ondataavailable = (event) => {
|
|
@@ -242,7 +248,7 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
242
248
|
session.resolveCapture(null);
|
|
243
249
|
return;
|
|
244
250
|
}
|
|
245
|
-
const type = recorder.mimeType || _optionalChain([session, 'access',
|
|
251
|
+
const type = recorder.mimeType || _optionalChain([session, 'access', _39 => _39.chunks, 'access', _40 => _40[0], 'optionalAccess', _41 => _41.type]) || mimeType || "audio/webm";
|
|
246
252
|
const blob = new Blob(session.chunks, { type });
|
|
247
253
|
session.blob = blob;
|
|
248
254
|
session.chunks = [];
|
|
@@ -250,16 +256,53 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
250
256
|
};
|
|
251
257
|
recorder.onerror = () => {
|
|
252
258
|
if (!isCurrent(session)) return;
|
|
259
|
+
session.liveFailed = true;
|
|
260
|
+
_optionalChain([session, 'access', _42 => _42.live, 'optionalAccess', _43 => _43.cancel, 'call', _44 => _44()]);
|
|
253
261
|
releaseTracks(session);
|
|
254
262
|
session.phase = "error";
|
|
255
263
|
session.resolveCapture(null);
|
|
256
264
|
setSnapshot({ ...EMPTY, state: "error", error: "Recording was interrupted. Try again." });
|
|
257
265
|
};
|
|
258
266
|
recorder.start();
|
|
267
|
+
if (startStream) {
|
|
268
|
+
const failed = (error) => {
|
|
269
|
+
if (!isCurrent(session) || session.phase === "ready" || session.phase === "error") return;
|
|
270
|
+
session.liveFailed = true;
|
|
271
|
+
_optionalChain([session, 'access', _45 => _45.live, 'optionalAccess', _46 => _46.cancel, 'call', _47 => _47()]);
|
|
272
|
+
if (transcribe) {
|
|
273
|
+
setSnapshot((previous) => ({ ...previous, previewError: "Live text is unavailable. Choose Done to transcribe this recording." }));
|
|
274
|
+
} else {
|
|
275
|
+
releaseTracks(session);
|
|
276
|
+
if (recorder.state !== "inactive") recorder.stop();
|
|
277
|
+
session.phase = "error";
|
|
278
|
+
setSnapshot((previous) => ({ ...previous, state: "error", error: error.message || "Live transcription stopped. Try again." }));
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
try {
|
|
282
|
+
const live = await startStream(stream, {
|
|
283
|
+
language: lang,
|
|
284
|
+
signal: session.streamController.signal,
|
|
285
|
+
onTranscript: (preview) => {
|
|
286
|
+
if (!isCurrent(session) || session.liveFailed || session.phase === "error" || session.phase === "ready") return;
|
|
287
|
+
session.transcript = preview.transcript;
|
|
288
|
+
session.interim = preview.interimTranscript;
|
|
289
|
+
setSnapshot((previous) => ({ ...previous, transcript: preview.transcript, interimTranscript: preview.interimTranscript }));
|
|
290
|
+
},
|
|
291
|
+
onError: failed
|
|
292
|
+
});
|
|
293
|
+
if (!isCurrent(session) || session.liveFailed || session.phase === "error") {
|
|
294
|
+
live.cancel();
|
|
295
|
+
} else session.live = live;
|
|
296
|
+
} catch (error) {
|
|
297
|
+
failed(error instanceof Error ? error : new Error("Live transcription could not start. Try again."));
|
|
298
|
+
}
|
|
299
|
+
if (!isCurrent(session) || session.phase !== "requesting") return;
|
|
300
|
+
}
|
|
259
301
|
session.phase = "recording";
|
|
260
302
|
session.stopMeter = meterStream(stream, session.audioLevels, () => isCurrent(session) && session.phase === "recording");
|
|
261
|
-
setSnapshot({ ...
|
|
303
|
+
setSnapshot((previous) => ({ ...previous, state: "recording" }));
|
|
262
304
|
} catch (error) {
|
|
305
|
+
_optionalChain([session, 'access', _48 => _48.live, 'optionalAccess', _49 => _49.cancel, 'call', _50 => _50()]);
|
|
263
306
|
releaseTracks(session);
|
|
264
307
|
if (!isCurrent(session)) return;
|
|
265
308
|
session.phase = "error";
|
|
@@ -295,7 +338,7 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
295
338
|
session.transcript = final.join(" ");
|
|
296
339
|
session.interim = interim.join(" ");
|
|
297
340
|
setSnapshot((previous) => ({ ...previous, transcript: session.transcript, interimTranscript: session.interim }));
|
|
298
|
-
if (chunks.length) _optionalChain([callback, 'access',
|
|
341
|
+
if (chunks.length) _optionalChain([callback, 'access', _51 => _51.current, 'optionalCall', _52 => _52(chunks.join(" "))]);
|
|
299
342
|
};
|
|
300
343
|
recognition.onend = () => {
|
|
301
344
|
session.nativeEnded = true;
|
|
@@ -320,23 +363,23 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
320
363
|
session.phase = "error";
|
|
321
364
|
setSnapshot({ ...EMPTY, state: "error", error: microphoneError(error) });
|
|
322
365
|
}
|
|
323
|
-
}, [cancel, continuous, isCurrent, lang, scope, scopeGeneration, transcribe]);
|
|
366
|
+
}, [cancel, continuous, isCurrent, lang, scope, scopeGeneration, transcribe, startStream]);
|
|
324
367
|
const finish = _react.useCallback.call(void 0, async (session) => {
|
|
325
368
|
if (!isCurrent(session)) return null;
|
|
326
369
|
releaseMeter(session);
|
|
327
370
|
session.phase = "transcribing";
|
|
328
371
|
setSnapshot((previous) => ({ ...previous, state: "transcribing", error: null, canRetry: false }));
|
|
329
|
-
if (!session.transcribe) {
|
|
372
|
+
if (!session.transcribe && !session.startStream) {
|
|
330
373
|
if (!session.nativeEnded) {
|
|
331
374
|
session.nativeTimer = setTimeout(() => {
|
|
332
375
|
try {
|
|
333
|
-
_optionalChain([session, 'access',
|
|
376
|
+
_optionalChain([session, 'access', _53 => _53.recognition, 'optionalAccess', _54 => _54.abort, 'call', _55 => _55()]);
|
|
334
377
|
} catch (e8) {
|
|
335
378
|
}
|
|
336
379
|
session.resolveNativeEnd();
|
|
337
380
|
}, 1e3);
|
|
338
381
|
try {
|
|
339
|
-
_optionalChain([session, 'access',
|
|
382
|
+
_optionalChain([session, 'access', _56 => _56.recognition, 'optionalAccess', _57 => _57.stop, 'call', _58 => _58()]);
|
|
340
383
|
} catch (e9) {
|
|
341
384
|
session.resolveNativeEnd();
|
|
342
385
|
}
|
|
@@ -352,13 +395,29 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
352
395
|
let text;
|
|
353
396
|
try {
|
|
354
397
|
if (!session.blob) {
|
|
355
|
-
if (_optionalChain([session, 'access',
|
|
398
|
+
if (_optionalChain([session, 'access', _59 => _59.recorder, 'optionalAccess', _60 => _60.state]) !== "inactive") _optionalChain([session, 'access', _61 => _61.recorder, 'optionalAccess', _62 => _62.stop, 'call', _63 => _63()]);
|
|
356
399
|
const blob = await session.capture;
|
|
357
400
|
if (!isCurrent(session) || !blob) return null;
|
|
358
401
|
session.blob = blob;
|
|
359
402
|
}
|
|
360
|
-
if (
|
|
361
|
-
|
|
403
|
+
if (session.live && !session.liveFailed) {
|
|
404
|
+
try {
|
|
405
|
+
const final = await session.live.finish();
|
|
406
|
+
if (session.liveFailed) throw new Error("Live transcription was interrupted. Please retry this recording.");
|
|
407
|
+
text = final.trim();
|
|
408
|
+
} catch (error) {
|
|
409
|
+
session.liveFailed = true;
|
|
410
|
+
if (!session.transcribe) throw error;
|
|
411
|
+
} finally {
|
|
412
|
+
session.live.cancel();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (!isCurrent(session)) return null;
|
|
416
|
+
if (text === void 0) {
|
|
417
|
+
if (!session.transcribe) throw new Error("Live transcription could not finish. Please record again.");
|
|
418
|
+
if (!session.blob.size) throw new Error("No audio was recorded. Try again.");
|
|
419
|
+
text = (await session.transcribe(session.blob, { language: session.language, signal: session.controller.signal })).trim();
|
|
420
|
+
}
|
|
362
421
|
if (!isCurrent(session)) return null;
|
|
363
422
|
if (!text) throw new Error("No speech was detected. Record again.");
|
|
364
423
|
} catch (error) {
|
|
@@ -366,13 +425,14 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
366
425
|
releaseTracks(session);
|
|
367
426
|
session.phase = "error";
|
|
368
427
|
const message = error instanceof Error && error.message ? error.message : "Transcription failed. Try again.";
|
|
369
|
-
|
|
428
|
+
_optionalChain([session, 'access', _64 => _64.live, 'optionalAccess', _65 => _65.cancel, 'call', _66 => _66()]);
|
|
429
|
+
setSnapshot((previous) => ({ ...previous, state: "error", error: message, canRetry: !!session.transcribe && !!_optionalChain([session, 'access', _67 => _67.blob, 'optionalAccess', _68 => _68.size]) }));
|
|
370
430
|
return null;
|
|
371
431
|
}
|
|
372
432
|
session.transcript = text;
|
|
373
433
|
session.phase = "ready";
|
|
374
434
|
setSnapshot({ ...EMPTY, state: "ready", transcript: text });
|
|
375
|
-
_optionalChain([callback, 'access',
|
|
435
|
+
_optionalChain([callback, 'access', _69 => _69.current, 'optionalCall', _70 => _70(text)]);
|
|
376
436
|
return text;
|
|
377
437
|
}, [isCurrent]);
|
|
378
438
|
const stop = _react.useCallback.call(void 0, () => {
|
|
@@ -383,8 +443,13 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
383
443
|
return Promise.resolve(null);
|
|
384
444
|
}
|
|
385
445
|
if (session.phase === "requesting") {
|
|
386
|
-
|
|
387
|
-
|
|
446
|
+
if (!session.transcribe || _optionalChain([session, 'access', _71 => _71.recorder, 'optionalAccess', _72 => _72.state]) !== "recording") {
|
|
447
|
+
cancel();
|
|
448
|
+
return Promise.resolve(null);
|
|
449
|
+
}
|
|
450
|
+
session.liveFailed = true;
|
|
451
|
+
_optionalChain([session, 'access', _73 => _73.streamController, 'optionalAccess', _74 => _74.abort, 'call', _75 => _75()]);
|
|
452
|
+
_optionalChain([session, 'access', _76 => _76.live, 'optionalAccess', _77 => _77.cancel, 'call', _78 => _78()]);
|
|
388
453
|
}
|
|
389
454
|
if (session.finish) return session.finish;
|
|
390
455
|
if (session.phase === "error") return Promise.resolve(null);
|
|
@@ -397,7 +462,7 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
397
462
|
cancel();
|
|
398
463
|
return Promise.resolve(null);
|
|
399
464
|
}
|
|
400
|
-
if (!_optionalChain([session, 'optionalAccess',
|
|
465
|
+
if (!_optionalChain([session, 'optionalAccess', _79 => _79.transcribe]) || !_optionalChain([session, 'access', _80 => _80.blob, 'optionalAccess', _81 => _81.size])) return Promise.resolve(null);
|
|
401
466
|
if (session.phase !== "error") return _nullishCoalesce(session.finish, () => ( Promise.resolve(null)));
|
|
402
467
|
session.finish = finish(session);
|
|
403
468
|
return session.finish;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "./chunk-G6SRSWXB.js";
|
|
11
11
|
import {
|
|
12
12
|
useDictation
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-RPSRCB5O.js";
|
|
14
14
|
|
|
15
15
|
// src/primitives/voice-dictation-button.tsx
|
|
16
16
|
import {
|
|
@@ -123,7 +123,7 @@ function VoiceDictationButton({
|
|
|
123
123
|
/* @__PURE__ */ jsx("h2", { className: cn("font-medium", anchored ? "text-sm" : "text-lg"), children: title || "Voice dictation" }),
|
|
124
124
|
/* @__PURE__ */ jsxs("span", { role: "status", className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
125
125
|
busy ? /* @__PURE__ */ jsx(LoaderCircle, { "aria-hidden": "true", className: "size-3.5 animate-spin motion-reduce:animate-none" }) : /* @__PURE__ */ jsx(Mic, { "aria-hidden": "true", className: "size-3.5" }),
|
|
126
|
-
capture.state === "transcribing" ? "
|
|
126
|
+
capture.state === "transcribing" ? "Finishing transcript\u2026" : capture.state === "requesting" ? "Starting dictation\u2026" : error ? "Recording not saved" : capture.state === "ready" ? "Ready" : caption
|
|
127
127
|
] })
|
|
128
128
|
] }),
|
|
129
129
|
/* @__PURE__ */ jsxs("time", { "aria-label": "Recording duration", dateTime: `PT${elapsed}S`, className: "pt-0.5 font-mono text-xs tabular-nums text-muted-foreground", children: [
|
|
@@ -134,7 +134,7 @@ function VoiceDictationButton({
|
|
|
134
134
|
] }),
|
|
135
135
|
/* @__PURE__ */ jsxs("div", { className: cn("flex min-h-0 flex-col gap-3", !anchored && "w-full max-w-xl flex-1 justify-center self-center py-8 text-center"), children: [
|
|
136
136
|
capture.audioLevels && /* @__PURE__ */ jsx(DictationWaveform, { source: capture.audioLevels, state: capture.state, className: !anchored ? "h-20" : void 0 }),
|
|
137
|
-
/* @__PURE__ */ jsx("p", { id: descriptionId, className: "text-xs text-muted-foreground", children: capture.state === "transcribing" ? "
|
|
137
|
+
/* @__PURE__ */ jsx("p", { id: descriptionId, className: "text-xs text-muted-foreground", children: capture.state === "transcribing" ? "Finishing your last words\u2026" : capture.state === "requesting" ? "Allow microphone access to start." : error ? capture.canRetry ? "Retry this take or record again." : "Try recording again." : "Speak, then choose Done to add the text." }),
|
|
138
138
|
displayTranscript && /* @__PURE__ */ jsxs("p", { className: "max-h-40 overflow-y-auto text-sm leading-relaxed", children: [
|
|
139
139
|
transcript,
|
|
140
140
|
interim && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
@@ -142,7 +142,8 @@ function VoiceDictationButton({
|
|
|
142
142
|
interim
|
|
143
143
|
] })
|
|
144
144
|
] }),
|
|
145
|
-
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-xs text-destructive", children: error })
|
|
145
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-xs text-destructive", children: error }),
|
|
146
|
+
!error && capture.previewError && /* @__PURE__ */ jsx("p", { role: "status", className: "text-xs text-muted-foreground", children: capture.previewError })
|
|
146
147
|
] }),
|
|
147
148
|
/* @__PURE__ */ jsxs("div", { className: cn("gap-2", anchored ? "flex flex-wrap items-center justify-end" : "mt-auto grid w-full max-w-xl grid-cols-2 gap-3 self-center"), children: [
|
|
148
149
|
/* @__PURE__ */ jsx(Button, { ref: cancelControl, type: "button", variant: "ghost", size: "sm", className: anchored ? "min-h-[44px]" : "min-h-[48px]", onClick: handleCancel, children: "Cancel" }),
|
|
@@ -10,7 +10,7 @@ var _chunkJDEXZGIYcjs = require('./chunk-JDEXZGIY.cjs');
|
|
|
10
10
|
var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
var
|
|
13
|
+
var _chunk7Q4EFCUIcjs = require('./chunk-7Q4EFCUI.cjs');
|
|
14
14
|
|
|
15
15
|
// src/primitives/voice-dictation-button.tsx
|
|
16
16
|
|
|
@@ -50,7 +50,7 @@ function VoiceDictationButton({
|
|
|
50
50
|
const isOpen = _react.useRef.call(void 0, open);
|
|
51
51
|
isOpen.current = open;
|
|
52
52
|
const descriptionId = _react.useId.call(void 0, );
|
|
53
|
-
const capture =
|
|
53
|
+
const capture = _chunk7Q4EFCUIcjs.useDictation.call(void 0, { lang, continuous });
|
|
54
54
|
const { transcript, interimTranscript: interim, supported, error } = capture;
|
|
55
55
|
const generation = _react.useRef.call(void 0, 0);
|
|
56
56
|
const finishing = _react.useRef.call(void 0, false);
|
|
@@ -123,7 +123,7 @@ function VoiceDictationButton({
|
|
|
123
123
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "font-medium", anchored ? "text-sm" : "text-lg"), children: title || "Voice dictation" }),
|
|
124
124
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { role: "status", className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
125
125
|
busy ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.LoaderCircle, { "aria-hidden": "true", className: "size-3.5 animate-spin motion-reduce:animate-none" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Mic, { "aria-hidden": "true", className: "size-3.5" }),
|
|
126
|
-
capture.state === "transcribing" ? "
|
|
126
|
+
capture.state === "transcribing" ? "Finishing transcript\u2026" : capture.state === "requesting" ? "Starting dictation\u2026" : error ? "Recording not saved" : capture.state === "ready" ? "Ready" : caption
|
|
127
127
|
] })
|
|
128
128
|
] }),
|
|
129
129
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "time", { "aria-label": "Recording duration", dateTime: `PT${elapsed}S`, className: "pt-0.5 font-mono text-xs tabular-nums text-muted-foreground", children: [
|
|
@@ -134,7 +134,7 @@ function VoiceDictationButton({
|
|
|
134
134
|
] }),
|
|
135
135
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "flex min-h-0 flex-col gap-3", !anchored && "w-full max-w-xl flex-1 justify-center self-center py-8 text-center"), children: [
|
|
136
136
|
capture.audioLevels && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DictationWaveform, { source: capture.audioLevels, state: capture.state, className: !anchored ? "h-20" : void 0 }),
|
|
137
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { id: descriptionId, className: "text-xs text-muted-foreground", children: capture.state === "transcribing" ? "
|
|
137
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { id: descriptionId, className: "text-xs text-muted-foreground", children: capture.state === "transcribing" ? "Finishing your last words\u2026" : capture.state === "requesting" ? "Allow microphone access to start." : error ? capture.canRetry ? "Retry this take or record again." : "Try recording again." : "Speak, then choose Done to add the text." }),
|
|
138
138
|
displayTranscript && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { className: "max-h-40 overflow-y-auto text-sm leading-relaxed", children: [
|
|
139
139
|
transcript,
|
|
140
140
|
interim && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "text-muted-foreground", children: [
|
|
@@ -142,7 +142,8 @@ function VoiceDictationButton({
|
|
|
142
142
|
interim
|
|
143
143
|
] })
|
|
144
144
|
] }),
|
|
145
|
-
error && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { role: "alert", className: "text-xs text-destructive", children: error })
|
|
145
|
+
error && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { role: "alert", className: "text-xs text-destructive", children: error }),
|
|
146
|
+
!error && capture.previewError && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { role: "status", className: "text-xs text-muted-foreground", children: capture.previewError })
|
|
146
147
|
] }),
|
|
147
148
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "gap-2", anchored ? "flex flex-wrap items-center justify-end" : "mt-auto grid w-full max-w-xl grid-cols-2 gap-3 self-center"), children: [
|
|
148
149
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { ref: cancelControl, type: "button", variant: "ghost", size: "sm", className: anchored ? "min-h-[44px]" : "min-h-[48px]", onClick: handleCancel, children: "Cancel" }),
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// src/hooks/use-dictation.ts
|
|
4
4
|
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
var DictationContext = createContext(null);
|
|
6
|
-
function DictationProvider({ transcribe, scopeKey, children }) {
|
|
6
|
+
function DictationProvider({ transcribe, stream, scopeKey, children }) {
|
|
7
7
|
const scope = useRef({ key: scopeKey, generation: 0 });
|
|
8
8
|
if (scope.current.key !== scopeKey) scope.current = { key: scopeKey, generation: scope.current.generation + 1 };
|
|
9
9
|
const generation = scope.current.generation;
|
|
10
|
-
const value = useMemo(() => ({ transcribe, scope, generation }), [transcribe, generation]);
|
|
10
|
+
const value = useMemo(() => ({ transcribe, stream, scope, generation }), [transcribe, stream, generation]);
|
|
11
11
|
return createElement(DictationContext.Provider, { value }, children);
|
|
12
12
|
}
|
|
13
13
|
var EMPTY = { state: "idle", transcript: "", interimTranscript: "", error: null, canRetry: false };
|
|
@@ -128,6 +128,8 @@ function releaseTracks(session) {
|
|
|
128
128
|
}
|
|
129
129
|
function dispose(session) {
|
|
130
130
|
session.controller.abort();
|
|
131
|
+
session.streamController?.abort();
|
|
132
|
+
session.live?.cancel();
|
|
131
133
|
if (session.nativeTimer) clearTimeout(session.nativeTimer);
|
|
132
134
|
const recorder = session.recorder;
|
|
133
135
|
if (recorder) {
|
|
@@ -158,6 +160,7 @@ function dispose(session) {
|
|
|
158
160
|
function useDictation({ lang = "en-US", continuous = true, onTranscript } = {}) {
|
|
159
161
|
const provider = useContext(DictationContext);
|
|
160
162
|
const transcribe = provider?.transcribe ?? null;
|
|
163
|
+
const startStream = provider?.stream ?? null;
|
|
161
164
|
const scope = provider?.scope ?? null;
|
|
162
165
|
const scopeGeneration = provider?.generation ?? 0;
|
|
163
166
|
const [snapshot, setSnapshot] = useState(EMPTY);
|
|
@@ -169,8 +172,8 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
169
172
|
activeScope.current = scope;
|
|
170
173
|
callback.current = onTranscript;
|
|
171
174
|
useEffect(() => {
|
|
172
|
-
setSupported(transcribe ? typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined" : typeof recognitionConstructor() === "function");
|
|
173
|
-
}, [transcribe]);
|
|
175
|
+
setSupported(transcribe || startStream ? typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined" : typeof recognitionConstructor() === "function");
|
|
176
|
+
}, [transcribe, startStream]);
|
|
174
177
|
const cancel = useCallback(() => {
|
|
175
178
|
const session = current.current;
|
|
176
179
|
current.current = null;
|
|
@@ -200,9 +203,12 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
200
203
|
const session = {
|
|
201
204
|
language: lang,
|
|
202
205
|
transcribe,
|
|
206
|
+
startStream,
|
|
207
|
+
liveFailed: false,
|
|
203
208
|
scope,
|
|
204
209
|
scopeGeneration,
|
|
205
210
|
controller: new AbortController(),
|
|
211
|
+
streamController: startStream ? new AbortController() : void 0,
|
|
206
212
|
phase: "requesting",
|
|
207
213
|
chunks: [],
|
|
208
214
|
capture: new Promise((resolve) => {
|
|
@@ -217,11 +223,11 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
217
223
|
transcript: "",
|
|
218
224
|
interim: "",
|
|
219
225
|
notified: /* @__PURE__ */ new Set(),
|
|
220
|
-
audioLevels: transcribe ? createAudioLevels() : void 0
|
|
226
|
+
audioLevels: transcribe || startStream ? createAudioLevels() : void 0
|
|
221
227
|
};
|
|
222
228
|
current.current = session;
|
|
223
229
|
setSnapshot({ ...EMPTY, state: "requesting" });
|
|
224
|
-
if (transcribe) {
|
|
230
|
+
if (transcribe || startStream) {
|
|
225
231
|
try {
|
|
226
232
|
if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === "undefined") throw new Error("unsupported");
|
|
227
233
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
@@ -250,16 +256,53 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
250
256
|
};
|
|
251
257
|
recorder.onerror = () => {
|
|
252
258
|
if (!isCurrent(session)) return;
|
|
259
|
+
session.liveFailed = true;
|
|
260
|
+
session.live?.cancel();
|
|
253
261
|
releaseTracks(session);
|
|
254
262
|
session.phase = "error";
|
|
255
263
|
session.resolveCapture(null);
|
|
256
264
|
setSnapshot({ ...EMPTY, state: "error", error: "Recording was interrupted. Try again." });
|
|
257
265
|
};
|
|
258
266
|
recorder.start();
|
|
267
|
+
if (startStream) {
|
|
268
|
+
const failed = (error) => {
|
|
269
|
+
if (!isCurrent(session) || session.phase === "ready" || session.phase === "error") return;
|
|
270
|
+
session.liveFailed = true;
|
|
271
|
+
session.live?.cancel();
|
|
272
|
+
if (transcribe) {
|
|
273
|
+
setSnapshot((previous) => ({ ...previous, previewError: "Live text is unavailable. Choose Done to transcribe this recording." }));
|
|
274
|
+
} else {
|
|
275
|
+
releaseTracks(session);
|
|
276
|
+
if (recorder.state !== "inactive") recorder.stop();
|
|
277
|
+
session.phase = "error";
|
|
278
|
+
setSnapshot((previous) => ({ ...previous, state: "error", error: error.message || "Live transcription stopped. Try again." }));
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
try {
|
|
282
|
+
const live = await startStream(stream, {
|
|
283
|
+
language: lang,
|
|
284
|
+
signal: session.streamController.signal,
|
|
285
|
+
onTranscript: (preview) => {
|
|
286
|
+
if (!isCurrent(session) || session.liveFailed || session.phase === "error" || session.phase === "ready") return;
|
|
287
|
+
session.transcript = preview.transcript;
|
|
288
|
+
session.interim = preview.interimTranscript;
|
|
289
|
+
setSnapshot((previous) => ({ ...previous, transcript: preview.transcript, interimTranscript: preview.interimTranscript }));
|
|
290
|
+
},
|
|
291
|
+
onError: failed
|
|
292
|
+
});
|
|
293
|
+
if (!isCurrent(session) || session.liveFailed || session.phase === "error") {
|
|
294
|
+
live.cancel();
|
|
295
|
+
} else session.live = live;
|
|
296
|
+
} catch (error) {
|
|
297
|
+
failed(error instanceof Error ? error : new Error("Live transcription could not start. Try again."));
|
|
298
|
+
}
|
|
299
|
+
if (!isCurrent(session) || session.phase !== "requesting") return;
|
|
300
|
+
}
|
|
259
301
|
session.phase = "recording";
|
|
260
302
|
session.stopMeter = meterStream(stream, session.audioLevels, () => isCurrent(session) && session.phase === "recording");
|
|
261
|
-
setSnapshot({ ...
|
|
303
|
+
setSnapshot((previous) => ({ ...previous, state: "recording" }));
|
|
262
304
|
} catch (error) {
|
|
305
|
+
session.live?.cancel();
|
|
263
306
|
releaseTracks(session);
|
|
264
307
|
if (!isCurrent(session)) return;
|
|
265
308
|
session.phase = "error";
|
|
@@ -320,13 +363,13 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
320
363
|
session.phase = "error";
|
|
321
364
|
setSnapshot({ ...EMPTY, state: "error", error: microphoneError(error) });
|
|
322
365
|
}
|
|
323
|
-
}, [cancel, continuous, isCurrent, lang, scope, scopeGeneration, transcribe]);
|
|
366
|
+
}, [cancel, continuous, isCurrent, lang, scope, scopeGeneration, transcribe, startStream]);
|
|
324
367
|
const finish = useCallback(async (session) => {
|
|
325
368
|
if (!isCurrent(session)) return null;
|
|
326
369
|
releaseMeter(session);
|
|
327
370
|
session.phase = "transcribing";
|
|
328
371
|
setSnapshot((previous) => ({ ...previous, state: "transcribing", error: null, canRetry: false }));
|
|
329
|
-
if (!session.transcribe) {
|
|
372
|
+
if (!session.transcribe && !session.startStream) {
|
|
330
373
|
if (!session.nativeEnded) {
|
|
331
374
|
session.nativeTimer = setTimeout(() => {
|
|
332
375
|
try {
|
|
@@ -357,8 +400,24 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
357
400
|
if (!isCurrent(session) || !blob) return null;
|
|
358
401
|
session.blob = blob;
|
|
359
402
|
}
|
|
360
|
-
if (
|
|
361
|
-
|
|
403
|
+
if (session.live && !session.liveFailed) {
|
|
404
|
+
try {
|
|
405
|
+
const final = await session.live.finish();
|
|
406
|
+
if (session.liveFailed) throw new Error("Live transcription was interrupted. Please retry this recording.");
|
|
407
|
+
text = final.trim();
|
|
408
|
+
} catch (error) {
|
|
409
|
+
session.liveFailed = true;
|
|
410
|
+
if (!session.transcribe) throw error;
|
|
411
|
+
} finally {
|
|
412
|
+
session.live.cancel();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (!isCurrent(session)) return null;
|
|
416
|
+
if (text === void 0) {
|
|
417
|
+
if (!session.transcribe) throw new Error("Live transcription could not finish. Please record again.");
|
|
418
|
+
if (!session.blob.size) throw new Error("No audio was recorded. Try again.");
|
|
419
|
+
text = (await session.transcribe(session.blob, { language: session.language, signal: session.controller.signal })).trim();
|
|
420
|
+
}
|
|
362
421
|
if (!isCurrent(session)) return null;
|
|
363
422
|
if (!text) throw new Error("No speech was detected. Record again.");
|
|
364
423
|
} catch (error) {
|
|
@@ -366,7 +425,8 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
366
425
|
releaseTracks(session);
|
|
367
426
|
session.phase = "error";
|
|
368
427
|
const message = error instanceof Error && error.message ? error.message : "Transcription failed. Try again.";
|
|
369
|
-
|
|
428
|
+
session.live?.cancel();
|
|
429
|
+
setSnapshot((previous) => ({ ...previous, state: "error", error: message, canRetry: !!session.transcribe && !!session.blob?.size }));
|
|
370
430
|
return null;
|
|
371
431
|
}
|
|
372
432
|
session.transcript = text;
|
|
@@ -383,8 +443,13 @@ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {})
|
|
|
383
443
|
return Promise.resolve(null);
|
|
384
444
|
}
|
|
385
445
|
if (session.phase === "requesting") {
|
|
386
|
-
|
|
387
|
-
|
|
446
|
+
if (!session.transcribe || session.recorder?.state !== "recording") {
|
|
447
|
+
cancel();
|
|
448
|
+
return Promise.resolve(null);
|
|
449
|
+
}
|
|
450
|
+
session.liveFailed = true;
|
|
451
|
+
session.streamController?.abort();
|
|
452
|
+
session.live?.cancel();
|
|
388
453
|
}
|
|
389
454
|
if (session.finish) return session.finish;
|
|
390
455
|
if (session.phase === "error") return Promise.resolve(null);
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunk7Q4EFCUIcjs = require('../chunk-7Q4EFCUI.cjs');
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
exports.DictationProvider =
|
|
10
|
+
exports.DictationProvider = _chunk7Q4EFCUIcjs.DictationProvider; exports.useDictation = _chunk7Q4EFCUIcjs.useDictation;
|
|
@@ -9,8 +9,27 @@ interface DictationTranscribeOptions {
|
|
|
9
9
|
}
|
|
10
10
|
/** The host authenticates this request; provider credentials never belong in the kit. */
|
|
11
11
|
type DictationTranscribe = (audio: Blob, options: DictationTranscribeOptions) => Promise<string>;
|
|
12
|
+
interface DictationStreamOptions extends DictationTranscribeOptions {
|
|
13
|
+
/** Replace the current preview. These updates must never save a note. */
|
|
14
|
+
onTranscript: (preview: {
|
|
15
|
+
transcript: string;
|
|
16
|
+
interimTranscript: string;
|
|
17
|
+
}) => void;
|
|
18
|
+
onError: (error: Error) => void;
|
|
19
|
+
}
|
|
20
|
+
interface DictationStreamSession {
|
|
21
|
+
/** Drain the final audio and resolve the complete, final transcript. */
|
|
22
|
+
finish: () => Promise<string>;
|
|
23
|
+
/** Release the transport without committing any text. Must be idempotent. */
|
|
24
|
+
cancel: () => void;
|
|
25
|
+
}
|
|
26
|
+
/** The host owns transport/authentication; use the supplied microphone stream. */
|
|
27
|
+
type DictationStream = (audio: MediaStream, options: DictationStreamOptions) => Promise<DictationStreamSession>;
|
|
12
28
|
interface DictationProviderProps {
|
|
13
|
-
|
|
29
|
+
/** Complete-recording transcription, also used to retry a failed stream. */
|
|
30
|
+
transcribe?: DictationTranscribe;
|
|
31
|
+
/** Optional live transcription. Preview events never invoke onTranscript. */
|
|
32
|
+
stream?: DictationStream;
|
|
14
33
|
/** Change when the acting user or workspace changes to discard its active take. */
|
|
15
34
|
scopeKey?: string;
|
|
16
35
|
children: ReactNode;
|
|
@@ -22,12 +41,13 @@ type DictationScope = {
|
|
|
22
41
|
};
|
|
23
42
|
};
|
|
24
43
|
type DictationContextValue = {
|
|
25
|
-
transcribe
|
|
44
|
+
transcribe?: DictationTranscribe;
|
|
45
|
+
stream?: DictationStream;
|
|
26
46
|
scope: DictationScope;
|
|
27
47
|
generation: number;
|
|
28
48
|
};
|
|
29
|
-
/** Use
|
|
30
|
-
declare function DictationProvider({ transcribe, scopeKey, children }: DictationProviderProps): React.FunctionComponentElement<React.ProviderProps<DictationContextValue | null>>;
|
|
49
|
+
/** Use the host's live or recorded-audio API for every descendant control. */
|
|
50
|
+
declare function DictationProvider({ transcribe, stream, scopeKey, children }: DictationProviderProps): React.FunctionComponentElement<React.ProviderProps<DictationContextValue | null>>;
|
|
31
51
|
type DictationState = "idle" | "requesting" | "recording" | "transcribing" | "ready" | "error";
|
|
32
52
|
interface UseDictationOptions {
|
|
33
53
|
lang?: string;
|
|
@@ -42,6 +62,8 @@ interface DictationCapture {
|
|
|
42
62
|
transcript: string;
|
|
43
63
|
interimTranscript: string;
|
|
44
64
|
error: string | null;
|
|
65
|
+
/** A live preview failed, but the recorded take can still be transcribed on Done. */
|
|
66
|
+
previewError?: string;
|
|
45
67
|
canRetry: boolean;
|
|
46
68
|
/** Live bands from the recording stream. Native speech recognition exposes no audio meter. */
|
|
47
69
|
audioLevels?: {
|
|
@@ -58,4 +80,4 @@ interface DictationCapture {
|
|
|
58
80
|
/** Shared microphone lifecycle. Without a provider, existing native dictation keeps working. */
|
|
59
81
|
declare function useDictation({ lang, continuous, onTranscript }?: UseDictationOptions): DictationCapture;
|
|
60
82
|
|
|
61
|
-
export { type DictationCapture, DictationProvider, type DictationProviderProps, type DictationState, type DictationTranscribe, type DictationTranscribeOptions, type UseDictationOptions, useDictation };
|
|
83
|
+
export { type DictationCapture, DictationProvider, type DictationProviderProps, type DictationState, type DictationStream, type DictationStreamOptions, type DictationStreamSession, type DictationTranscribe, type DictationTranscribeOptions, type UseDictationOptions, useDictation };
|
|
@@ -9,8 +9,27 @@ interface DictationTranscribeOptions {
|
|
|
9
9
|
}
|
|
10
10
|
/** The host authenticates this request; provider credentials never belong in the kit. */
|
|
11
11
|
type DictationTranscribe = (audio: Blob, options: DictationTranscribeOptions) => Promise<string>;
|
|
12
|
+
interface DictationStreamOptions extends DictationTranscribeOptions {
|
|
13
|
+
/** Replace the current preview. These updates must never save a note. */
|
|
14
|
+
onTranscript: (preview: {
|
|
15
|
+
transcript: string;
|
|
16
|
+
interimTranscript: string;
|
|
17
|
+
}) => void;
|
|
18
|
+
onError: (error: Error) => void;
|
|
19
|
+
}
|
|
20
|
+
interface DictationStreamSession {
|
|
21
|
+
/** Drain the final audio and resolve the complete, final transcript. */
|
|
22
|
+
finish: () => Promise<string>;
|
|
23
|
+
/** Release the transport without committing any text. Must be idempotent. */
|
|
24
|
+
cancel: () => void;
|
|
25
|
+
}
|
|
26
|
+
/** The host owns transport/authentication; use the supplied microphone stream. */
|
|
27
|
+
type DictationStream = (audio: MediaStream, options: DictationStreamOptions) => Promise<DictationStreamSession>;
|
|
12
28
|
interface DictationProviderProps {
|
|
13
|
-
|
|
29
|
+
/** Complete-recording transcription, also used to retry a failed stream. */
|
|
30
|
+
transcribe?: DictationTranscribe;
|
|
31
|
+
/** Optional live transcription. Preview events never invoke onTranscript. */
|
|
32
|
+
stream?: DictationStream;
|
|
14
33
|
/** Change when the acting user or workspace changes to discard its active take. */
|
|
15
34
|
scopeKey?: string;
|
|
16
35
|
children: ReactNode;
|
|
@@ -22,12 +41,13 @@ type DictationScope = {
|
|
|
22
41
|
};
|
|
23
42
|
};
|
|
24
43
|
type DictationContextValue = {
|
|
25
|
-
transcribe
|
|
44
|
+
transcribe?: DictationTranscribe;
|
|
45
|
+
stream?: DictationStream;
|
|
26
46
|
scope: DictationScope;
|
|
27
47
|
generation: number;
|
|
28
48
|
};
|
|
29
|
-
/** Use
|
|
30
|
-
declare function DictationProvider({ transcribe, scopeKey, children }: DictationProviderProps): React.FunctionComponentElement<React.ProviderProps<DictationContextValue | null>>;
|
|
49
|
+
/** Use the host's live or recorded-audio API for every descendant control. */
|
|
50
|
+
declare function DictationProvider({ transcribe, stream, scopeKey, children }: DictationProviderProps): React.FunctionComponentElement<React.ProviderProps<DictationContextValue | null>>;
|
|
31
51
|
type DictationState = "idle" | "requesting" | "recording" | "transcribing" | "ready" | "error";
|
|
32
52
|
interface UseDictationOptions {
|
|
33
53
|
lang?: string;
|
|
@@ -42,6 +62,8 @@ interface DictationCapture {
|
|
|
42
62
|
transcript: string;
|
|
43
63
|
interimTranscript: string;
|
|
44
64
|
error: string | null;
|
|
65
|
+
/** A live preview failed, but the recorded take can still be transcribed on Done. */
|
|
66
|
+
previewError?: string;
|
|
45
67
|
canRetry: boolean;
|
|
46
68
|
/** Live bands from the recording stream. Native speech recognition exposes no audio meter. */
|
|
47
69
|
audioLevels?: {
|
|
@@ -58,4 +80,4 @@ interface DictationCapture {
|
|
|
58
80
|
/** Shared microphone lifecycle. Without a provider, existing native dictation keeps working. */
|
|
59
81
|
declare function useDictation({ lang, continuous, onTranscript }?: UseDictationOptions): DictationCapture;
|
|
60
82
|
|
|
61
|
-
export { type DictationCapture, DictationProvider, type DictationProviderProps, type DictationState, type DictationTranscribe, type DictationTranscribeOptions, type UseDictationOptions, useDictation };
|
|
83
|
+
export { type DictationCapture, DictationProvider, type DictationProviderProps, type DictationState, type DictationStream, type DictationStreamOptions, type DictationStreamSession, type DictationTranscribe, type DictationTranscribeOptions, type UseDictationOptions, useDictation };
|
package/dist/primitives/chat.cjs
CHANGED
|
@@ -10,7 +10,7 @@ var _chunkYMI3X2YLcjs = require('../chunk-YMI3X2YL.cjs');
|
|
|
10
10
|
var _chunk2BTDBZYNcjs = require('../chunk-2BTDBZYN.cjs');
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
var
|
|
13
|
+
var _chunkQWUMIAYDcjs = require('../chunk-QWUMIAYD.cjs');
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
@@ -86,7 +86,7 @@ require('../chunk-2BSFYU22.cjs');
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
var _chunk4LXN6GLZcjs = require('../chunk-4LXN6GLZ.cjs');
|
|
89
|
-
require('../chunk-
|
|
89
|
+
require('../chunk-7Q4EFCUI.cjs');
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
var _chunkR35FT53Gcjs = require('../chunk-R35FT53G.cjs');
|
|
@@ -402,8 +402,9 @@ function ChatInput({
|
|
|
402
402
|
const [editing, setEditing] = _react.useState.call(void 0, false);
|
|
403
403
|
const focusVersion = _react.useRef.call(void 0, 0);
|
|
404
404
|
const isDisabled = pending || disabled;
|
|
405
|
+
const hasText = Boolean(value.trim());
|
|
405
406
|
const attachmentsReady = attachments.length > 0 && attachments.every((item) => item.state !== "uploading");
|
|
406
|
-
const canSubmit = !isDisabled && (
|
|
407
|
+
const canSubmit = !isDisabled && (hasText || attachmentsReady);
|
|
407
408
|
const liveVoiceActive = voiceSession && voiceSession.state !== "disconnected" && voiceSession.state !== "error";
|
|
408
409
|
const submit = () => {
|
|
409
410
|
const trimmed = value.trim();
|
|
@@ -541,7 +542,7 @@ function ChatInput({
|
|
|
541
542
|
) : null,
|
|
542
543
|
(!helperText || compact) && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex-1" }),
|
|
543
544
|
voice && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
544
|
-
|
|
545
|
+
_chunkQWUMIAYDcjs.VoiceDictationButton,
|
|
545
546
|
{
|
|
546
547
|
size: "icon",
|
|
547
548
|
title: voiceTitle,
|
|
@@ -555,8 +556,7 @@ function ChatInput({
|
|
|
555
556
|
)
|
|
556
557
|
}
|
|
557
558
|
),
|
|
558
|
-
voiceSession && !liveVoiceActive && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkYMI3X2YLcjs.VoiceSessionControls, { ...voiceSession, disabled: voiceSession.disabled || isDisabled }),
|
|
559
|
-
submitLabel ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
559
|
+
voiceSession && !liveVoiceActive && !hasText && attachments.length === 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkYMI3X2YLcjs.VoiceSessionControls, { ...voiceSession, disabled: voiceSession.disabled || isDisabled }) : submitLabel ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
560
560
|
_chunkJDEXZGIYcjs.Button,
|
|
561
561
|
{
|
|
562
562
|
type: "submit",
|
|
@@ -572,11 +572,15 @@ function ChatInput({
|
|
|
572
572
|
_chunkJDEXZGIYcjs.Button,
|
|
573
573
|
{
|
|
574
574
|
type: "submit",
|
|
575
|
-
size: spacious && !compact ? "icon-lg" : "icon",
|
|
575
|
+
size: !voiceSession && spacious && !compact ? "icon-lg" : "icon",
|
|
576
|
+
shape: voiceSession ? "circle" : void 0,
|
|
576
577
|
disabled: !canSubmit,
|
|
577
578
|
"aria-label": "Send",
|
|
578
579
|
"data-chat-action": "submit",
|
|
579
|
-
className: _chunkKWLSBTB2cjs.cn.call(void 0,
|
|
580
|
+
className: _chunkKWLSBTB2cjs.cn.call(void 0,
|
|
581
|
+
spacious && !voiceSession && "rounded-xl",
|
|
582
|
+
(voiceSession || compactOnBlur) && "min-h-[44px] min-w-[44px] shrink-0"
|
|
583
|
+
),
|
|
580
584
|
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.ArrowUp, { className: _chunkKWLSBTB2cjs.cn.call(void 0, spacious && "h-5 w-5") })
|
|
581
585
|
}
|
|
582
586
|
)
|
|
@@ -616,7 +620,7 @@ function VoiceHero({
|
|
|
616
620
|
voiceSession ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
617
621
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkYMI3X2YLcjs.VoiceSessionControls, { ...voiceSession, size: "hero" }),
|
|
618
622
|
voice && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
619
|
-
|
|
623
|
+
_chunkQWUMIAYDcjs.VoiceDictationButton,
|
|
620
624
|
{
|
|
621
625
|
size: "icon",
|
|
622
626
|
title: voiceTitle,
|
|
@@ -626,7 +630,7 @@ function VoiceHero({
|
|
|
626
630
|
}
|
|
627
631
|
)
|
|
628
632
|
] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
629
|
-
|
|
633
|
+
_chunkQWUMIAYDcjs.VoiceDictationButton,
|
|
630
634
|
{
|
|
631
635
|
size: "lg",
|
|
632
636
|
title: voiceTitle,
|
|
@@ -154,7 +154,11 @@ interface ChatInputProps {
|
|
|
154
154
|
uploadingLabel?: string;
|
|
155
155
|
/** Show the inline voice dictation button. Defaults to true. */
|
|
156
156
|
voice?: boolean;
|
|
157
|
-
/**
|
|
157
|
+
/**
|
|
158
|
+
* Live voice controls, separate from dictation. With no text or staged files,
|
|
159
|
+
* the round voice action replaces Send; an active call keeps its controls
|
|
160
|
+
* above the input. The host owns the transport and lifecycle.
|
|
161
|
+
*/
|
|
158
162
|
voiceSession?: VoiceSessionControlsProps;
|
|
159
163
|
/** Title shown inside the voice overlay. */
|
|
160
164
|
voiceTitle?: string;
|
|
@@ -164,6 +168,7 @@ interface ChatInputProps {
|
|
|
164
168
|
* Label for the submit button. When set, the button renders as a labelled
|
|
165
169
|
* primary action (`{label} →`) instead of the default icon-only send — use
|
|
166
170
|
* it to name the outcome, e.g. "Propose 3 times", "Create link", "Send".
|
|
171
|
+
* An empty composer with live voice still shows the voice action first.
|
|
167
172
|
*/
|
|
168
173
|
submitLabel?: ReactNode;
|
|
169
174
|
/** Auto-focus the textarea on mount. */
|
|
@@ -200,9 +205,10 @@ interface ChatInputProps {
|
|
|
200
205
|
/**
|
|
201
206
|
* Compact text input: a form around one bordered input group, with any
|
|
202
207
|
* staged files on top, a textarea that grows with its text, then an action
|
|
203
|
-
* bar
|
|
204
|
-
*
|
|
205
|
-
*
|
|
208
|
+
* bar ending in one primary action. When live voice is available, an empty
|
|
209
|
+
* composer shows its round start action; text or staged files replace it
|
|
210
|
+
* with Send. Dictation remains a separate mic. Enter sends; Shift+Enter
|
|
211
|
+
* newlines, and Enter that commits an IME candidate never sends or starts a call.
|
|
206
212
|
*
|
|
207
213
|
* The submit button is icon-only by default; pass `submitLabel` to name the
|
|
208
214
|
* outcome ("Propose 3 times", "Create link") — it then renders as a labelled
|
|
@@ -294,7 +300,8 @@ interface ChatProps {
|
|
|
294
300
|
* Label for the submit button (empty-state input). When set, the button
|
|
295
301
|
* names the outcome (`{label} →`) instead of the icon-only send — e.g.
|
|
296
302
|
* "Propose 3 times". Once the conversation is active the input reverts to
|
|
297
|
-
* the compact icon send.
|
|
303
|
+
* the compact icon send. With live voice available, an empty composer shows
|
|
304
|
+
* the voice action until text or files are staged.
|
|
298
305
|
*/
|
|
299
306
|
submitLabel?: ReactNode;
|
|
300
307
|
/** Files currently staged in the composer. */
|
|
@@ -357,7 +364,7 @@ interface ChatProps {
|
|
|
357
364
|
fileDropDescription?: ReactNode;
|
|
358
365
|
/** Enable voice dictation. Defaults to true. */
|
|
359
366
|
voice?: boolean;
|
|
360
|
-
/** Controlled live voice UI
|
|
367
|
+
/** Controlled live voice UI; its empty-input action is separate from the dictation mic. */
|
|
361
368
|
voiceSession?: VoiceSessionControlsProps;
|
|
362
369
|
/** Title shown inside the voice overlay. */
|
|
363
370
|
voiceTitle?: string;
|
|
@@ -154,7 +154,11 @@ interface ChatInputProps {
|
|
|
154
154
|
uploadingLabel?: string;
|
|
155
155
|
/** Show the inline voice dictation button. Defaults to true. */
|
|
156
156
|
voice?: boolean;
|
|
157
|
-
/**
|
|
157
|
+
/**
|
|
158
|
+
* Live voice controls, separate from dictation. With no text or staged files,
|
|
159
|
+
* the round voice action replaces Send; an active call keeps its controls
|
|
160
|
+
* above the input. The host owns the transport and lifecycle.
|
|
161
|
+
*/
|
|
158
162
|
voiceSession?: VoiceSessionControlsProps;
|
|
159
163
|
/** Title shown inside the voice overlay. */
|
|
160
164
|
voiceTitle?: string;
|
|
@@ -164,6 +168,7 @@ interface ChatInputProps {
|
|
|
164
168
|
* Label for the submit button. When set, the button renders as a labelled
|
|
165
169
|
* primary action (`{label} →`) instead of the default icon-only send — use
|
|
166
170
|
* it to name the outcome, e.g. "Propose 3 times", "Create link", "Send".
|
|
171
|
+
* An empty composer with live voice still shows the voice action first.
|
|
167
172
|
*/
|
|
168
173
|
submitLabel?: ReactNode;
|
|
169
174
|
/** Auto-focus the textarea on mount. */
|
|
@@ -200,9 +205,10 @@ interface ChatInputProps {
|
|
|
200
205
|
/**
|
|
201
206
|
* Compact text input: a form around one bordered input group, with any
|
|
202
207
|
* staged files on top, a textarea that grows with its text, then an action
|
|
203
|
-
* bar
|
|
204
|
-
*
|
|
205
|
-
*
|
|
208
|
+
* bar ending in one primary action. When live voice is available, an empty
|
|
209
|
+
* composer shows its round start action; text or staged files replace it
|
|
210
|
+
* with Send. Dictation remains a separate mic. Enter sends; Shift+Enter
|
|
211
|
+
* newlines, and Enter that commits an IME candidate never sends or starts a call.
|
|
206
212
|
*
|
|
207
213
|
* The submit button is icon-only by default; pass `submitLabel` to name the
|
|
208
214
|
* outcome ("Propose 3 times", "Create link") — it then renders as a labelled
|
|
@@ -294,7 +300,8 @@ interface ChatProps {
|
|
|
294
300
|
* Label for the submit button (empty-state input). When set, the button
|
|
295
301
|
* names the outcome (`{label} →`) instead of the icon-only send — e.g.
|
|
296
302
|
* "Propose 3 times". Once the conversation is active the input reverts to
|
|
297
|
-
* the compact icon send.
|
|
303
|
+
* the compact icon send. With live voice available, an empty composer shows
|
|
304
|
+
* the voice action until text or files are staged.
|
|
298
305
|
*/
|
|
299
306
|
submitLabel?: ReactNode;
|
|
300
307
|
/** Files currently staged in the composer. */
|
|
@@ -357,7 +364,7 @@ interface ChatProps {
|
|
|
357
364
|
fileDropDescription?: ReactNode;
|
|
358
365
|
/** Enable voice dictation. Defaults to true. */
|
|
359
366
|
voice?: boolean;
|
|
360
|
-
/** Controlled live voice UI
|
|
367
|
+
/** Controlled live voice UI; its empty-input action is separate from the dictation mic. */
|
|
361
368
|
voiceSession?: VoiceSessionControlsProps;
|
|
362
369
|
/** Title shown inside the voice overlay. */
|
|
363
370
|
voiceTitle?: string;
|
package/dist/primitives/chat.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "../chunk-Z45YKVAM.js";
|
|
11
11
|
import {
|
|
12
12
|
VoiceDictationButton
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-JKVVTFAP.js";
|
|
14
14
|
import {
|
|
15
15
|
Suggestion,
|
|
16
16
|
Suggestions
|
|
@@ -86,7 +86,7 @@ import "../chunk-GJ7S3L6R.js";
|
|
|
86
86
|
import {
|
|
87
87
|
useCopyToClipboard
|
|
88
88
|
} from "../chunk-ZWZAAGQF.js";
|
|
89
|
-
import "../chunk-
|
|
89
|
+
import "../chunk-RPSRCB5O.js";
|
|
90
90
|
import {
|
|
91
91
|
useIsMobile
|
|
92
92
|
} from "../chunk-PIU5SSUV.js";
|
|
@@ -402,8 +402,9 @@ function ChatInput({
|
|
|
402
402
|
const [editing, setEditing] = useState(false);
|
|
403
403
|
const focusVersion = useRef(0);
|
|
404
404
|
const isDisabled = pending || disabled;
|
|
405
|
+
const hasText = Boolean(value.trim());
|
|
405
406
|
const attachmentsReady = attachments.length > 0 && attachments.every((item) => item.state !== "uploading");
|
|
406
|
-
const canSubmit = !isDisabled && (
|
|
407
|
+
const canSubmit = !isDisabled && (hasText || attachmentsReady);
|
|
407
408
|
const liveVoiceActive = voiceSession && voiceSession.state !== "disconnected" && voiceSession.state !== "error";
|
|
408
409
|
const submit = () => {
|
|
409
410
|
const trimmed = value.trim();
|
|
@@ -555,8 +556,7 @@ function ChatInput({
|
|
|
555
556
|
)
|
|
556
557
|
}
|
|
557
558
|
),
|
|
558
|
-
voiceSession && !liveVoiceActive && /* @__PURE__ */ jsx(VoiceSessionControls, { ...voiceSession, disabled: voiceSession.disabled || isDisabled })
|
|
559
|
-
submitLabel ? /* @__PURE__ */ jsxs(
|
|
559
|
+
voiceSession && !liveVoiceActive && !hasText && attachments.length === 0 ? /* @__PURE__ */ jsx(VoiceSessionControls, { ...voiceSession, disabled: voiceSession.disabled || isDisabled }) : submitLabel ? /* @__PURE__ */ jsxs(
|
|
560
560
|
Button,
|
|
561
561
|
{
|
|
562
562
|
type: "submit",
|
|
@@ -572,11 +572,15 @@ function ChatInput({
|
|
|
572
572
|
Button,
|
|
573
573
|
{
|
|
574
574
|
type: "submit",
|
|
575
|
-
size: spacious && !compact ? "icon-lg" : "icon",
|
|
575
|
+
size: !voiceSession && spacious && !compact ? "icon-lg" : "icon",
|
|
576
|
+
shape: voiceSession ? "circle" : void 0,
|
|
576
577
|
disabled: !canSubmit,
|
|
577
578
|
"aria-label": "Send",
|
|
578
579
|
"data-chat-action": "submit",
|
|
579
|
-
className: cn(
|
|
580
|
+
className: cn(
|
|
581
|
+
spacious && !voiceSession && "rounded-xl",
|
|
582
|
+
(voiceSession || compactOnBlur) && "min-h-[44px] min-w-[44px] shrink-0"
|
|
583
|
+
),
|
|
580
584
|
children: /* @__PURE__ */ jsx(ArrowUp, { className: cn(spacious && "h-5 w-5") })
|
|
581
585
|
}
|
|
582
586
|
)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunk7Q4EFCUIcjs = require('../chunk-7Q4EFCUI.cjs');
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
exports.DictationProvider =
|
|
8
|
+
exports.DictationProvider = _chunk7Q4EFCUIcjs.DictationProvider;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { DictationProvider, DictationProviderProps, DictationTranscribe, DictationTranscribeOptions } from '../hooks/use-dictation.cjs';
|
|
1
|
+
export { DictationProvider, DictationProviderProps, DictationStream, DictationStreamOptions, DictationStreamSession, DictationTranscribe, DictationTranscribeOptions } from '../hooks/use-dictation.cjs';
|
|
2
2
|
import 'react';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { DictationProvider, DictationProviderProps, DictationTranscribe, DictationTranscribeOptions } from '../hooks/use-dictation.js';
|
|
1
|
+
export { DictationProvider, DictationProviderProps, DictationStream, DictationStreamOptions, DictationStreamSession, DictationTranscribe, DictationTranscribeOptions } from '../hooks/use-dictation.js';
|
|
2
2
|
import 'react';
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkQWUMIAYDcjs = require('../chunk-QWUMIAYD.cjs');
|
|
6
6
|
require('../chunk-NKWHDI4C.cjs');
|
|
7
7
|
require('../chunk-JDEXZGIY.cjs');
|
|
8
8
|
require('../chunk-KWLSBTB2.cjs');
|
|
9
9
|
require('../chunk-2BSFYU22.cjs');
|
|
10
|
-
require('../chunk-
|
|
10
|
+
require('../chunk-7Q4EFCUI.cjs');
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
exports.VoiceDictationButton =
|
|
13
|
+
exports.VoiceDictationButton = _chunkQWUMIAYDcjs.VoiceDictationButton;
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
import {
|
|
4
4
|
VoiceDictationButton
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-JKVVTFAP.js";
|
|
6
6
|
import "../chunk-5OF6D3GH.js";
|
|
7
7
|
import "../chunk-W326GKML.js";
|
|
8
8
|
import "../chunk-G6SRSWXB.js";
|
|
9
9
|
import "../chunk-GJ7S3L6R.js";
|
|
10
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-RPSRCB5O.js";
|
|
11
11
|
export {
|
|
12
12
|
VoiceDictationButton
|
|
13
13
|
};
|
package/package.json
CHANGED