@epicbagel/saas-kit 1.11.1 → 1.13.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 +41 -4
- package/THIRD_PARTY_NOTICES.md +20 -0
- package/dist/{chunk-ZZ7ICQR2.cjs → chunk-7Q4EFCUI.cjs} +93 -28
- package/dist/{chunk-EDEN7TAL.js → chunk-JKVVTFAP.js} +5 -4
- package/dist/chunk-KI7SWNIN.js +53 -0
- package/dist/{chunk-D47AMKT4.cjs → chunk-QWUMIAYD.cjs} +6 -5
- package/dist/{chunk-HLAHOVEI.js → chunk-RPSRCB5O.js} +79 -14
- package/dist/chunk-SJB4DPGQ.cjs +51 -0
- package/dist/chunk-SRIGG5I5.js +51 -0
- package/dist/chunk-VKYRB37O.cjs +53 -0
- 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 +16 -12
- package/dist/primitives/chat.d.cts +13 -6
- package/dist/primitives/chat.d.ts +13 -6
- package/dist/primitives/chat.js +13 -9
- 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/notes.cjs +259 -0
- package/dist/primitives/notes.d.cts +123 -0
- package/dist/primitives/notes.d.ts +123 -0
- package/dist/primitives/notes.js +259 -0
- package/dist/primitives/panel-section.cjs +4 -48
- package/dist/primitives/panel-section.js +3 -47
- package/dist/primitives/section-header.cjs +4 -45
- package/dist/primitives/section-header.js +4 -45
- package/dist/primitives/voice-dictation-button.cjs +3 -3
- package/dist/primitives/voice-dictation-button.js +2 -2
- package/package.json +11 -1
|
@@ -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);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunkVGTINL5Qcjs = require('./chunk-VGTINL5Q.cjs');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
|
|
8
|
+
|
|
9
|
+
// src/primitives/section-header.tsx
|
|
10
|
+
var _react = require('react');
|
|
11
|
+
var _classvarianceauthority = require('class-variance-authority');
|
|
12
|
+
var _jsxruntime = require('react/jsx-runtime');
|
|
13
|
+
var sectionHeaderVariants = _classvarianceauthority.cva.call(void 0, "flex items-center gap-1", {
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
muted: "text-xs font-semibold text-muted-foreground uppercase tracking-wide",
|
|
17
|
+
plain: "text-sm font-medium text-foreground"
|
|
18
|
+
},
|
|
19
|
+
spacing: {
|
|
20
|
+
tight: "mb-2",
|
|
21
|
+
loose: "mb-3",
|
|
22
|
+
none: ""
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: "muted",
|
|
27
|
+
spacing: "tight"
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
var SectionHeader = _react.forwardRef.call(void 0,
|
|
31
|
+
function SectionHeader2({ icon, label, count, action, variant, spacing, className, ...rest }, ref) {
|
|
32
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
33
|
+
"h3",
|
|
34
|
+
{
|
|
35
|
+
ref,
|
|
36
|
+
className: _chunkKWLSBTB2cjs.cn.call(void 0, sectionHeaderVariants({ variant, spacing }), className),
|
|
37
|
+
...rest,
|
|
38
|
+
children: [
|
|
39
|
+
icon,
|
|
40
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: label }),
|
|
41
|
+
typeof count === "number" && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVGTINL5Qcjs.CountBadge, { value: count, variant: "secondary" }),
|
|
42
|
+
action && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "ml-auto", children: action })
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
exports.SectionHeader = SectionHeader;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
CountBadge
|
|
4
|
+
} from "./chunk-H7TETFLB.js";
|
|
5
|
+
import {
|
|
6
|
+
cn
|
|
7
|
+
} from "./chunk-G6SRSWXB.js";
|
|
8
|
+
|
|
9
|
+
// src/primitives/section-header.tsx
|
|
10
|
+
import { forwardRef } from "react";
|
|
11
|
+
import { cva } from "class-variance-authority";
|
|
12
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
var sectionHeaderVariants = cva("flex items-center gap-1", {
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
muted: "text-xs font-semibold text-muted-foreground uppercase tracking-wide",
|
|
17
|
+
plain: "text-sm font-medium text-foreground"
|
|
18
|
+
},
|
|
19
|
+
spacing: {
|
|
20
|
+
tight: "mb-2",
|
|
21
|
+
loose: "mb-3",
|
|
22
|
+
none: ""
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: "muted",
|
|
27
|
+
spacing: "tight"
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
var SectionHeader = forwardRef(
|
|
31
|
+
function SectionHeader2({ icon, label, count, action, variant, spacing, className, ...rest }, ref) {
|
|
32
|
+
return /* @__PURE__ */ jsxs(
|
|
33
|
+
"h3",
|
|
34
|
+
{
|
|
35
|
+
ref,
|
|
36
|
+
className: cn(sectionHeaderVariants({ variant, spacing }), className),
|
|
37
|
+
...rest,
|
|
38
|
+
children: [
|
|
39
|
+
icon,
|
|
40
|
+
/* @__PURE__ */ jsx("span", { children: label }),
|
|
41
|
+
typeof count === "number" && /* @__PURE__ */ jsx(CountBadge, { value: count, variant: "secondary" }),
|
|
42
|
+
action && /* @__PURE__ */ jsx("span", { className: "ml-auto", children: action })
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
SectionHeader
|
|
51
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
|
|
5
|
+
|
|
6
|
+
// src/primitives/panel-section.tsx
|
|
7
|
+
var _react = require('react');
|
|
8
|
+
var _jsxruntime = require('react/jsx-runtime');
|
|
9
|
+
var borders = {
|
|
10
|
+
none: "",
|
|
11
|
+
top: "border-t",
|
|
12
|
+
bottom: "border-b"
|
|
13
|
+
};
|
|
14
|
+
var paddings = {
|
|
15
|
+
none: "",
|
|
16
|
+
compact: "px-[22px] py-[18px]",
|
|
17
|
+
default: "px-[26px] py-[26px]"
|
|
18
|
+
};
|
|
19
|
+
var PanelSection = _react.forwardRef.call(void 0,
|
|
20
|
+
function PanelSection2({ as, border = "none", padding = "default", paddingX = "none", paddingTop = "none", paddingBottom = "none", paddingY = "none", surface = "default", layout = "block", gap = "none", shrink = true, className, ...props }, ref) {
|
|
21
|
+
const Component = _nullishCoalesce(as, () => ( "section"));
|
|
22
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
23
|
+
Component,
|
|
24
|
+
{
|
|
25
|
+
ref,
|
|
26
|
+
className: _chunkKWLSBTB2cjs.cn.call(void 0,
|
|
27
|
+
borders[border],
|
|
28
|
+
paddings[padding],
|
|
29
|
+
paddingX === "md" && "px-[22px]",
|
|
30
|
+
paddingX === "lg" && "px-[26px]",
|
|
31
|
+
paddingTop === "sm" && "pt-2",
|
|
32
|
+
paddingTop === "md" && "pt-3",
|
|
33
|
+
paddingTop === "lg" && "pt-5",
|
|
34
|
+
paddingBottom === "xs" && "pb-1",
|
|
35
|
+
paddingBottom === "sm" && "pb-2",
|
|
36
|
+
paddingBottom === "md" && "pb-3",
|
|
37
|
+
paddingBottom === "lg" && "pb-4",
|
|
38
|
+
paddingY === "xl" && "py-12",
|
|
39
|
+
surface === "muted" && "bg-muted/40",
|
|
40
|
+
layout === "row" && "flex",
|
|
41
|
+
gap === "sm" && "gap-2",
|
|
42
|
+
!shrink && "shrink-0",
|
|
43
|
+
className
|
|
44
|
+
),
|
|
45
|
+
...props
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
exports.PanelSection = PanelSection;
|
|
@@ -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
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkQWUMIAYDcjs = require('../chunk-QWUMIAYD.cjs');
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
var _chunkYMI3X2YLcjs = require('../chunk-YMI3X2YL.cjs');
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
var _chunk2BTDBZYNcjs = require('../chunk-2BTDBZYN.cjs');
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
var
|
|
13
|
+
var _chunk2BTDBZYNcjs = require('../chunk-2BTDBZYN.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;
|