@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.
Files changed (30) hide show
  1. package/README.md +34 -12
  2. package/dist/{chunk-ZZ7ICQR2.cjs → chunk-7Q4EFCUI.cjs} +93 -28
  3. package/dist/chunk-JKVVTFAP.js +294 -0
  4. package/dist/{chunk-UOVA4YKW.js → chunk-KF4X37DP.js} +2 -15
  5. package/dist/chunk-QWUMIAYD.cjs +294 -0
  6. package/dist/{chunk-HLAHOVEI.js → chunk-RPSRCB5O.js} +79 -14
  7. package/dist/{chunk-7QTFYEPE.cjs → chunk-YMI3X2YL.cjs} +2 -15
  8. package/dist/hooks/use-dictation.cjs +2 -2
  9. package/dist/hooks/use-dictation.d.cts +27 -5
  10. package/dist/hooks/use-dictation.d.ts +27 -5
  11. package/dist/hooks/use-dictation.js +1 -1
  12. package/dist/primitives/chat.cjs +17 -13
  13. package/dist/primitives/chat.d.cts +13 -6
  14. package/dist/primitives/chat.d.ts +13 -6
  15. package/dist/primitives/chat.js +12 -8
  16. package/dist/primitives/dictation-provider.cjs +2 -2
  17. package/dist/primitives/dictation-provider.d.cts +1 -1
  18. package/dist/primitives/dictation-provider.d.ts +1 -1
  19. package/dist/primitives/dictation-provider.js +1 -1
  20. package/dist/primitives/voice-dictation-button.cjs +3 -3
  21. package/dist/primitives/voice-dictation-button.d.cts +8 -6
  22. package/dist/primitives/voice-dictation-button.d.ts +8 -6
  23. package/dist/primitives/voice-dictation-button.js +2 -2
  24. package/dist/primitives/voice-session-controls.cjs +2 -2
  25. package/dist/primitives/voice-session-controls.d.cts +2 -2
  26. package/dist/primitives/voice-session-controls.d.ts +2 -2
  27. package/dist/primitives/voice-session-controls.js +1 -1
  28. package/package.json +1 -1
  29. package/dist/chunk-KQBXAQAY.cjs +0 -221
  30. 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__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
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({ ...EMPTY, state: "recording" });
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 (!session.blob.size) throw new Error("No audio was recorded. Try again.");
361
- text = (await session.transcribe(session.blob, { language: session.language, signal: session.controller.signal })).trim();
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
- setSnapshot({ ...EMPTY, state: "error", error: message, canRetry: !!session.blob?.size });
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
- cancel();
387
- return Promise.resolve(null);
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.jsxs.call(void 0, "div", { className: "min-w-0 flex-1 space-y-1", children: [
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 _chunkZZ7ICQR2cjs = require('../chunk-ZZ7ICQR2.cjs');
6
+ var _chunk7Q4EFCUIcjs = require('../chunk-7Q4EFCUI.cjs');
7
7
 
8
8
 
9
9
 
10
- exports.DictationProvider = _chunkZZ7ICQR2cjs.DictationProvider; exports.useDictation = _chunkZZ7ICQR2cjs.useDictation;
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
- transcribe: DictationTranscribe;
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: DictationTranscribe;
44
+ transcribe?: DictationTranscribe;
45
+ stream?: DictationStream;
26
46
  scope: DictationScope;
27
47
  generation: number;
28
48
  };
29
- /** Use recorded audio + the host's API for every descendant dictation control. */
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 };