@epicbagel/saas-kit 1.11.0 → 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 +34 -12
- package/dist/{chunk-ZZ7ICQR2.cjs → chunk-7Q4EFCUI.cjs} +93 -28
- package/dist/chunk-JKVVTFAP.js +294 -0
- package/dist/{chunk-UOVA4YKW.js → chunk-KF4X37DP.js} +2 -15
- package/dist/chunk-QWUMIAYD.cjs +294 -0
- package/dist/{chunk-HLAHOVEI.js → chunk-RPSRCB5O.js} +79 -14
- package/dist/{chunk-7QTFYEPE.cjs → chunk-YMI3X2YL.cjs} +2 -15
- 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 +17 -13
- package/dist/primitives/chat.d.cts +13 -6
- package/dist/primitives/chat.d.ts +13 -6
- package/dist/primitives/chat.js +12 -8
- 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.d.cts +8 -6
- package/dist/primitives/voice-dictation-button.d.ts +8 -6
- package/dist/primitives/voice-dictation-button.js +2 -2
- package/dist/primitives/voice-session-controls.cjs +2 -2
- package/dist/primitives/voice-session-controls.d.cts +2 -2
- package/dist/primitives/voice-session-controls.d.ts +2 -2
- package/dist/primitives/voice-session-controls.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-KQBXAQAY.cjs +0 -221
- package/dist/chunk-T3XMHWYV.js +0 -221
|
@@ -33,8 +33,7 @@ var DEFAULT_LABELS = {
|
|
|
33
33
|
retry: "Retry voice chat",
|
|
34
34
|
endShort: "End voice",
|
|
35
35
|
muteShort: "Mute",
|
|
36
|
-
unmuteShort: "Unmute"
|
|
37
|
-
transcriptPlaceholder: "Speak to continue the conversation."
|
|
36
|
+
unmuteShort: "Unmute"
|
|
38
37
|
};
|
|
39
38
|
function VoiceSessionControls({
|
|
40
39
|
state,
|
|
@@ -44,7 +43,6 @@ function VoiceSessionControls({
|
|
|
44
43
|
onMutedChange,
|
|
45
44
|
disabled = false,
|
|
46
45
|
error,
|
|
47
|
-
transcript,
|
|
48
46
|
volumeBands,
|
|
49
47
|
audioLevels,
|
|
50
48
|
labels,
|
|
@@ -79,18 +77,7 @@ function VoiceSessionControls({
|
|
|
79
77
|
volumeBands: muted && state === "listening" ? SILENCE : volumeBands ?? liveBands
|
|
80
78
|
}
|
|
81
79
|
),
|
|
82
|
-
/* @__PURE__ */
|
|
83
|
-
/* @__PURE__ */ jsx("span", { role: "status", className: "block text-sm font-medium text-primary", children: muted && state === "listening" ? copy.muted : copy[state] }),
|
|
84
|
-
/* @__PURE__ */ jsx(
|
|
85
|
-
"p",
|
|
86
|
-
{
|
|
87
|
-
"data-slot": "voice-session-transcript",
|
|
88
|
-
title: transcript || void 0,
|
|
89
|
-
className: cn("truncate text-sm font-normal", transcript ? "text-foreground" : "text-muted-foreground"),
|
|
90
|
-
children: transcript || copy.transcriptPlaceholder
|
|
91
|
-
}
|
|
92
|
-
)
|
|
93
|
-
] })
|
|
80
|
+
/* @__PURE__ */ jsx("span", { role: "status", className: "min-w-0 flex-1 text-sm font-medium text-primary", children: muted && state === "listening" ? copy.muted : copy[state] })
|
|
94
81
|
] }),
|
|
95
82
|
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
|
|
96
83
|
/* @__PURE__ */ jsxs(
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunkNKWHDI4Ccjs = require('./chunk-NKWHDI4C.cjs');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
var _chunkJDEXZGIYcjs = require('./chunk-JDEXZGIY.cjs');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
var _chunk7Q4EFCUIcjs = require('./chunk-7Q4EFCUI.cjs');
|
|
14
|
+
|
|
15
|
+
// src/primitives/voice-dictation-button.tsx
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
var _react = require('react');
|
|
24
|
+
var _reactdialog = require('@radix-ui/react-dialog'); var DialogPrimitive = _interopRequireWildcard(_reactdialog);
|
|
25
|
+
var _reactpopover = require('@radix-ui/react-popover'); var PopoverPrimitive = _interopRequireWildcard(_reactpopover);
|
|
26
|
+
var _lucidereact = require('lucide-react');
|
|
27
|
+
var _jsxruntime = require('react/jsx-runtime');
|
|
28
|
+
function VoiceDictationButton({
|
|
29
|
+
onCommit,
|
|
30
|
+
onCancel,
|
|
31
|
+
title = "Dictate your message",
|
|
32
|
+
caption = "Recording",
|
|
33
|
+
lang = "en-US",
|
|
34
|
+
continuous = true,
|
|
35
|
+
className,
|
|
36
|
+
size = "sm",
|
|
37
|
+
showLabel = false,
|
|
38
|
+
children,
|
|
39
|
+
disabled = false,
|
|
40
|
+
visibility = "visible",
|
|
41
|
+
shrink = true,
|
|
42
|
+
anchorRef
|
|
43
|
+
}) {
|
|
44
|
+
const [open, setOpen] = _react.useState.call(void 0, false);
|
|
45
|
+
const [anchor, setAnchor] = _react.useState.call(void 0, null);
|
|
46
|
+
const desktop = _react.useSyncExternalStore.call(void 0, subscribeDesktop, isDesktop, () => false);
|
|
47
|
+
const anchored = desktop && isVisibleAnchor(anchor);
|
|
48
|
+
const placement = _react.useRef.call(void 0, anchored);
|
|
49
|
+
placement.current = anchored;
|
|
50
|
+
const isOpen = _react.useRef.call(void 0, open);
|
|
51
|
+
isOpen.current = open;
|
|
52
|
+
const descriptionId = _react.useId.call(void 0, );
|
|
53
|
+
const capture = _chunk7Q4EFCUIcjs.useDictation.call(void 0, { lang, continuous });
|
|
54
|
+
const { transcript, interimTranscript: interim, supported, error } = capture;
|
|
55
|
+
const generation = _react.useRef.call(void 0, 0);
|
|
56
|
+
const finishing = _react.useRef.call(void 0, false);
|
|
57
|
+
const previousFocus = _react.useRef.call(void 0, null);
|
|
58
|
+
const cancelControl = _react.useRef.call(void 0, null);
|
|
59
|
+
const elapsed = useRecordingTime(capture.state);
|
|
60
|
+
const busy = capture.state === "requesting" || capture.state === "transcribing";
|
|
61
|
+
const openOverlay = _react.useCallback.call(void 0, (event) => {
|
|
62
|
+
const focused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
63
|
+
const explicit = _optionalChain([anchorRef, 'optionalAccess', _ => _.current]);
|
|
64
|
+
const invokingButton = _optionalChain([focused, 'optionalAccess', _2 => _2.matches, 'call', _3 => _3("button:not(:disabled), [role='button']:not([aria-disabled='true'])")]) ? focused : null;
|
|
65
|
+
const resolved = isVisibleAnchor(explicit) ? explicit : isVisibleAnchor(event.currentTarget) ? event.currentTarget : isVisibleAnchor(invokingButton) ? invokingButton : null;
|
|
66
|
+
previousFocus.current = _optionalChain([resolved, 'optionalAccess', _4 => _4.matches, 'call', _5 => _5("button, a[href], input, select, textarea, [tabindex]")]) ? resolved : focused;
|
|
67
|
+
setAnchor(resolved);
|
|
68
|
+
generation.current++;
|
|
69
|
+
finishing.current = false;
|
|
70
|
+
setOpen(true);
|
|
71
|
+
void capture.start();
|
|
72
|
+
}, [anchorRef, capture.start]);
|
|
73
|
+
const closeOverlay = _react.useCallback.call(void 0, () => {
|
|
74
|
+
generation.current++;
|
|
75
|
+
finishing.current = false;
|
|
76
|
+
capture.cancel();
|
|
77
|
+
setOpen(false);
|
|
78
|
+
}, [capture.cancel]);
|
|
79
|
+
const handleDone = _react.useCallback.call(void 0, async () => {
|
|
80
|
+
if (finishing.current || busy) return;
|
|
81
|
+
_optionalChain([cancelControl, 'access', _6 => _6.current, 'optionalAccess', _7 => _7.focus, 'call', _8 => _8({ preventScroll: true })]);
|
|
82
|
+
if (capture.state === "error" && !capture.canRetry) {
|
|
83
|
+
void capture.start();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
finishing.current = true;
|
|
87
|
+
const started = generation.current;
|
|
88
|
+
const final = await (capture.canRetry ? capture.retry() : capture.stop());
|
|
89
|
+
if (started !== generation.current) return;
|
|
90
|
+
finishing.current = false;
|
|
91
|
+
if (final === null) return;
|
|
92
|
+
closeOverlay();
|
|
93
|
+
if (final) onCommit(final);
|
|
94
|
+
else _optionalChain([onCancel, 'optionalCall', _9 => _9()]);
|
|
95
|
+
}, [busy, capture.canRetry, capture.retry, capture.start, capture.state, capture.stop, onCommit, onCancel, closeOverlay]);
|
|
96
|
+
const handleCancel = _react.useCallback.call(void 0, () => {
|
|
97
|
+
closeOverlay();
|
|
98
|
+
_optionalChain([onCancel, 'optionalCall', _10 => _10()]);
|
|
99
|
+
}, [closeOverlay, onCancel]);
|
|
100
|
+
_react.useEffect.call(void 0, () => {
|
|
101
|
+
if (open && capture.state === "idle") handleCancel();
|
|
102
|
+
}, [capture.state, handleCancel, open]);
|
|
103
|
+
_react.useEffect.call(void 0, () => {
|
|
104
|
+
return () => {
|
|
105
|
+
generation.current++;
|
|
106
|
+
};
|
|
107
|
+
}, []);
|
|
108
|
+
if (!supported) return null;
|
|
109
|
+
const displayTranscript = `${transcript}${interim ? (transcript ? " " : "") + interim : ""}`;
|
|
110
|
+
const closeAutoFocus = (event) => {
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
if (!isOpen.current && _optionalChain([previousFocus, 'access', _11 => _11.current, 'optionalAccess', _12 => _12.isConnected])) previousFocus.current.focus();
|
|
113
|
+
};
|
|
114
|
+
const onKeyDown = (event) => {
|
|
115
|
+
if (event.key === "Enter" && !event.shiftKey && !(event.target instanceof HTMLElement && event.target.closest("button, input, textarea, select"))) {
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
void handleDone();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const content = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
121
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "flex min-w-0 items-start justify-between gap-3", !anchored && "w-full max-w-xl self-center"), children: [
|
|
122
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "min-w-0 space-y-1", children: [
|
|
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
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { role: "status", className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
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" ? "Finishing transcript\u2026" : capture.state === "requesting" ? "Starting dictation\u2026" : error ? "Recording not saved" : capture.state === "ready" ? "Ready" : caption
|
|
127
|
+
] })
|
|
128
|
+
] }),
|
|
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: [
|
|
130
|
+
Math.floor(elapsed / 60).toString().padStart(2, "0"),
|
|
131
|
+
":",
|
|
132
|
+
(elapsed % 60).toString().padStart(2, "0")
|
|
133
|
+
] })
|
|
134
|
+
] }),
|
|
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
|
+
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" ? "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
|
+
displayTranscript && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { className: "max-h-40 overflow-y-auto text-sm leading-relaxed", children: [
|
|
139
|
+
transcript,
|
|
140
|
+
interim && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "text-muted-foreground", children: [
|
|
141
|
+
transcript ? " " : "",
|
|
142
|
+
interim
|
|
143
|
+
] })
|
|
144
|
+
] }),
|
|
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 })
|
|
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: [
|
|
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" }),
|
|
150
|
+
capture.canRetry && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { type: "button", variant: "outline", size: "sm", className: anchored ? "min-h-[44px]" : "order-first col-span-2 min-h-[48px]", onClick: () => {
|
|
151
|
+
_optionalChain([cancelControl, 'access', _13 => _13.current, 'optionalAccess', _14 => _14.focus, 'call', _15 => _15({ preventScroll: true })]);
|
|
152
|
+
void capture.start();
|
|
153
|
+
}, children: "Record again" }),
|
|
154
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { type: "button", size: "sm", className: anchored ? "min-h-[44px]" : "min-h-[48px]", onClick: () => void handleDone(), disabled: busy, children: capture.state === "transcribing" ? "Transcribing\u2026" : capture.canRetry ? "Retry transcription" : error ? "Try again" : "Done" })
|
|
155
|
+
] })
|
|
156
|
+
] });
|
|
157
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
158
|
+
children ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
159
|
+
"button",
|
|
160
|
+
{
|
|
161
|
+
type: "button",
|
|
162
|
+
onClick: openOverlay,
|
|
163
|
+
disabled,
|
|
164
|
+
"aria-label": "Start dictation",
|
|
165
|
+
className: _chunkKWLSBTB2cjs.cn.call(void 0, visibility === "hidden" && "hidden", className),
|
|
166
|
+
children
|
|
167
|
+
}
|
|
168
|
+
) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
169
|
+
_chunkJDEXZGIYcjs.Button,
|
|
170
|
+
{
|
|
171
|
+
type: "button",
|
|
172
|
+
onClick: openOverlay,
|
|
173
|
+
disabled,
|
|
174
|
+
variant: "outline",
|
|
175
|
+
size,
|
|
176
|
+
"aria-label": "Start dictation",
|
|
177
|
+
className: _chunkKWLSBTB2cjs.cn.call(void 0, "transition-colors", visibility === "hidden" && "hidden", !shrink && "shrink-0", className),
|
|
178
|
+
children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex items-center justify-center gap-2", children: [
|
|
179
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Mic, { className: "h-4 w-4" }),
|
|
180
|
+
showLabel && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: "Speak" })
|
|
181
|
+
] })
|
|
182
|
+
}
|
|
183
|
+
),
|
|
184
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, PopoverPrimitive.Root, { modal: true, open: open && anchored, onOpenChange: (next) => {
|
|
185
|
+
if (!next && placement.current) handleCancel();
|
|
186
|
+
}, children: [
|
|
187
|
+
anchored && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, PopoverPrimitive.Anchor, { virtualRef: { current: anchor } }),
|
|
188
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, PopoverPrimitive.Portal, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
189
|
+
PopoverPrimitive.Content,
|
|
190
|
+
{
|
|
191
|
+
"aria-label": "Voice dictation",
|
|
192
|
+
"aria-describedby": descriptionId,
|
|
193
|
+
"data-slot": "voice-dictation-panel",
|
|
194
|
+
"data-state": capture.state,
|
|
195
|
+
"data-placement": "anchored",
|
|
196
|
+
side: "top",
|
|
197
|
+
align: "center",
|
|
198
|
+
sideOffset: 8,
|
|
199
|
+
collisionPadding: 12,
|
|
200
|
+
avoidCollisions: true,
|
|
201
|
+
onCloseAutoFocus: closeAutoFocus,
|
|
202
|
+
onKeyDown,
|
|
203
|
+
onEscapeKeyDown: (event) => event.stopPropagation(),
|
|
204
|
+
className: "z-[200] flex max-h-[var(--radix-popover-content-available-height)] w-96 max-w-[calc(100vw-1.5rem)] flex-col gap-3 overflow-y-auto rounded-2xl border border-border bg-background p-4 text-foreground shadow-lg outline-hidden",
|
|
205
|
+
children: content
|
|
206
|
+
}
|
|
207
|
+
) })
|
|
208
|
+
] }),
|
|
209
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, DialogPrimitive.Root, { open: open && !anchored, onOpenChange: (next) => {
|
|
210
|
+
if (!next && !placement.current) handleCancel();
|
|
211
|
+
}, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, DialogPrimitive.Portal, { children: [
|
|
212
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, DialogPrimitive.Overlay, { className: "fixed inset-0 z-[199] bg-background" }),
|
|
213
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
214
|
+
DialogPrimitive.Content,
|
|
215
|
+
{
|
|
216
|
+
"aria-label": "Voice dictation",
|
|
217
|
+
"aria-labelledby": void 0,
|
|
218
|
+
"data-slot": "voice-dictation-panel",
|
|
219
|
+
"data-state": capture.state,
|
|
220
|
+
"data-placement": "fullscreen",
|
|
221
|
+
onCloseAutoFocus: closeAutoFocus,
|
|
222
|
+
onKeyDown,
|
|
223
|
+
onEscapeKeyDown: (event) => event.stopPropagation(),
|
|
224
|
+
className: "fixed inset-0 z-[200] flex h-dvh w-screen flex-col gap-6 overflow-y-auto overscroll-contain bg-background px-6 pt-[max(1.5rem,env(safe-area-inset-top))] pb-[max(1.5rem,env(safe-area-inset-bottom))] text-foreground outline-hidden",
|
|
225
|
+
children: [
|
|
226
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, DialogPrimitive.Title, { className: "sr-only", children: "Voice dictation" }),
|
|
227
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, DialogPrimitive.Description, { className: "sr-only", children: "Record your message, then choose Done to add the text. Cancel discards the recording." }),
|
|
228
|
+
content
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
] }) })
|
|
233
|
+
] });
|
|
234
|
+
}
|
|
235
|
+
var SILENCE = [];
|
|
236
|
+
var silence = () => SILENCE;
|
|
237
|
+
function DictationWaveform({ source, state, className }) {
|
|
238
|
+
const bands = _react.useSyncExternalStore.call(void 0, source.subscribe, source.getSnapshot, silence);
|
|
239
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
240
|
+
_chunkNKWHDI4Ccjs.AgentAudioVisualizerBar,
|
|
241
|
+
{
|
|
242
|
+
"aria-hidden": "true",
|
|
243
|
+
"data-slot": "dictation-waveform",
|
|
244
|
+
size: "icon",
|
|
245
|
+
barCount: 24,
|
|
246
|
+
state: state === "recording" ? "listening" : state === "transcribing" ? "thinking" : state === "requesting" ? "connecting" : "disconnected",
|
|
247
|
+
volumeBands: state === "recording" ? bands : void 0,
|
|
248
|
+
className: _chunkKWLSBTB2cjs.cn.call(void 0, "h-9 w-full text-foreground", className)
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
function useRecordingTime(state) {
|
|
253
|
+
const [elapsed, setElapsed] = _react.useState.call(void 0, 0);
|
|
254
|
+
const startedAt = _react.useRef.call(void 0, null);
|
|
255
|
+
_react.useEffect.call(void 0, () => {
|
|
256
|
+
if (state !== "recording") {
|
|
257
|
+
if (state === "idle" || state === "requesting") setElapsed(0);
|
|
258
|
+
else if (startedAt.current !== null) setElapsed(Math.floor((performance.now() - startedAt.current) / 1e3));
|
|
259
|
+
startedAt.current = null;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
startedAt.current = performance.now();
|
|
263
|
+
const update = () => setElapsed(Math.floor((performance.now() - startedAt.current) / 1e3));
|
|
264
|
+
update();
|
|
265
|
+
const timer = setInterval(update, 1e3);
|
|
266
|
+
return () => {
|
|
267
|
+
clearInterval(timer);
|
|
268
|
+
};
|
|
269
|
+
}, [state]);
|
|
270
|
+
return elapsed;
|
|
271
|
+
}
|
|
272
|
+
var DESKTOP_QUERY = "(min-width: 768px)";
|
|
273
|
+
function isDesktop() {
|
|
274
|
+
return typeof window !== "undefined" && !!_optionalChain([window, 'access', _16 => _16.matchMedia, 'optionalCall', _17 => _17(DESKTOP_QUERY), 'access', _18 => _18.matches]);
|
|
275
|
+
}
|
|
276
|
+
function subscribeDesktop(changed) {
|
|
277
|
+
const media = _optionalChain([window, 'access', _19 => _19.matchMedia, 'optionalCall', _20 => _20(DESKTOP_QUERY)]);
|
|
278
|
+
_optionalChain([media, 'optionalAccess', _21 => _21.addEventListener, 'call', _22 => _22("change", changed)]);
|
|
279
|
+
return () => _optionalChain([media, 'optionalAccess', _23 => _23.removeEventListener, 'call', _24 => _24("change", changed)]);
|
|
280
|
+
}
|
|
281
|
+
function isVisibleAnchor(element) {
|
|
282
|
+
if (!_optionalChain([element, 'optionalAccess', _25 => _25.isConnected]) || !element.getClientRects().length) return false;
|
|
283
|
+
const rect = element.getBoundingClientRect();
|
|
284
|
+
if (!rect.width || !rect.height) return false;
|
|
285
|
+
for (let parent = element; parent; parent = parent.parentElement) {
|
|
286
|
+
const style = getComputedStyle(parent);
|
|
287
|
+
if (parent.hidden || style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse" || Number.parseFloat(style.opacity) === 0) return false;
|
|
288
|
+
}
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
exports.VoiceDictationButton = VoiceDictationButton;
|
|
@@ -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);
|
|
@@ -33,8 +33,7 @@ var DEFAULT_LABELS = {
|
|
|
33
33
|
retry: "Retry voice chat",
|
|
34
34
|
endShort: "End voice",
|
|
35
35
|
muteShort: "Mute",
|
|
36
|
-
unmuteShort: "Unmute"
|
|
37
|
-
transcriptPlaceholder: "Speak to continue the conversation."
|
|
36
|
+
unmuteShort: "Unmute"
|
|
38
37
|
};
|
|
39
38
|
function VoiceSessionControls({
|
|
40
39
|
state,
|
|
@@ -44,7 +43,6 @@ function VoiceSessionControls({
|
|
|
44
43
|
onMutedChange,
|
|
45
44
|
disabled = false,
|
|
46
45
|
error,
|
|
47
|
-
transcript,
|
|
48
46
|
volumeBands,
|
|
49
47
|
audioLevels,
|
|
50
48
|
labels,
|
|
@@ -79,18 +77,7 @@ function VoiceSessionControls({
|
|
|
79
77
|
volumeBands: muted && state === "listening" ? SILENCE : _nullishCoalesce(volumeBands, () => ( liveBands))
|
|
80
78
|
}
|
|
81
79
|
),
|
|
82
|
-
/* @__PURE__ */ _jsxruntime.
|
|
83
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { role: "status", className: "block text-sm font-medium text-primary", children: muted && state === "listening" ? copy.muted : copy[state] }),
|
|
84
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
85
|
-
"p",
|
|
86
|
-
{
|
|
87
|
-
"data-slot": "voice-session-transcript",
|
|
88
|
-
title: transcript || void 0,
|
|
89
|
-
className: _chunkKWLSBTB2cjs.cn.call(void 0, "truncate text-sm font-normal", transcript ? "text-foreground" : "text-muted-foreground"),
|
|
90
|
-
children: transcript || copy.transcriptPlaceholder
|
|
91
|
-
}
|
|
92
|
-
)
|
|
93
|
-
] })
|
|
80
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { role: "status", className: "min-w-0 flex-1 text-sm font-medium text-primary", children: muted && state === "listening" ? copy.muted : copy[state] })
|
|
94
81
|
] }),
|
|
95
82
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
|
|
96
83
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
@@ -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 };
|