@epicbagel/saas-kit 1.7.0 → 1.11.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 (103) hide show
  1. package/README.md +72 -1
  2. package/THIRD_PARTY_NOTICES.md +14 -0
  3. package/dist/chunk-2BSFYU22.cjs +71 -0
  4. package/dist/chunk-5OF6D3GH.js +158 -0
  5. package/dist/chunk-7QTFYEPE.cjs +153 -0
  6. package/dist/{chunk-GTVSRRE3.cjs → chunk-BGCNGYPL.cjs} +2 -2
  7. package/dist/{chunk-QTWJQQDH.cjs → chunk-FBVZRRA4.cjs} +6 -6
  8. package/dist/chunk-GJ7S3L6R.js +71 -0
  9. package/dist/chunk-HLAHOVEI.js +412 -0
  10. package/dist/{chunk-F3B4M6DU.js → chunk-HTPPNW74.js} +1 -1
  11. package/dist/chunk-KQBXAQAY.cjs +221 -0
  12. package/dist/{chunk-JN6JOSZG.js → chunk-LQPJGZIE.js} +7 -7
  13. package/dist/chunk-NKWHDI4C.cjs +158 -0
  14. package/dist/chunk-T3XMHWYV.js +221 -0
  15. package/dist/chunk-UOVA4YKW.js +153 -0
  16. package/dist/chunk-ZZ7ICQR2.cjs +412 -0
  17. package/dist/hooks/use-agent-audio-visualizer-bar.cjs +8 -0
  18. package/dist/hooks/use-agent-audio-visualizer-bar.d.cts +4 -0
  19. package/dist/hooks/use-agent-audio-visualizer-bar.d.ts +4 -0
  20. package/dist/hooks/use-agent-audio-visualizer-bar.js +8 -0
  21. package/dist/hooks/use-dictation.cjs +10 -0
  22. package/dist/hooks/use-dictation.d.cts +61 -0
  23. package/dist/hooks/use-dictation.d.ts +61 -0
  24. package/dist/hooks/use-dictation.js +10 -0
  25. package/dist/primitives/activity-feed.cjs +3 -3
  26. package/dist/primitives/activity-feed.js +4 -4
  27. package/dist/primitives/agent-audio-visualizer-bar.cjs +16 -0
  28. package/dist/primitives/agent-audio-visualizer-bar.d.cts +75 -0
  29. package/dist/primitives/agent-audio-visualizer-bar.d.ts +75 -0
  30. package/dist/primitives/agent-audio-visualizer-bar.js +16 -0
  31. package/dist/primitives/alert.d.cts +1 -1
  32. package/dist/primitives/alert.d.ts +1 -1
  33. package/dist/primitives/archive-confirmation-dialog.cjs +2 -2
  34. package/dist/primitives/archive-confirmation-dialog.js +3 -3
  35. package/dist/primitives/attachment.d.cts +1 -1
  36. package/dist/primitives/attachment.d.ts +1 -1
  37. package/dist/primitives/badge.d.cts +1 -1
  38. package/dist/primitives/badge.d.ts +1 -1
  39. package/dist/primitives/board-view.cjs +6 -6
  40. package/dist/primitives/board-view.js +5 -5
  41. package/dist/primitives/bubble.d.cts +1 -1
  42. package/dist/primitives/bubble.d.ts +1 -1
  43. package/dist/primitives/button.d.cts +2 -2
  44. package/dist/primitives/button.d.ts +2 -2
  45. package/dist/primitives/chat.cjs +59 -28
  46. package/dist/primitives/chat.d.cts +8 -3
  47. package/dist/primitives/chat.d.ts +8 -3
  48. package/dist/primitives/chat.js +56 -25
  49. package/dist/primitives/code-block.cjs +4 -4
  50. package/dist/primitives/code-block.js +3 -3
  51. package/dist/primitives/connection.cjs +6 -6
  52. package/dist/primitives/connection.js +7 -7
  53. package/dist/primitives/copy-button.cjs +3 -3
  54. package/dist/primitives/copy-button.js +2 -2
  55. package/dist/primitives/dictation-provider.cjs +8 -0
  56. package/dist/primitives/dictation-provider.d.cts +2 -0
  57. package/dist/primitives/dictation-provider.d.ts +2 -0
  58. package/dist/primitives/dictation-provider.js +8 -0
  59. package/dist/primitives/editable-list.cjs +1 -1
  60. package/dist/primitives/editable-list.js +1 -1
  61. package/dist/primitives/file-dropzone.cjs +3 -3
  62. package/dist/primitives/file-dropzone.js +2 -2
  63. package/dist/primitives/file-preview.cjs +6 -6
  64. package/dist/primitives/file-preview.js +5 -5
  65. package/dist/primitives/filter-bar.cjs +4 -4
  66. package/dist/primitives/filter-bar.js +3 -3
  67. package/dist/primitives/input-group.d.cts +1 -1
  68. package/dist/primitives/input-group.d.ts +1 -1
  69. package/dist/primitives/item.d.cts +2 -2
  70. package/dist/primitives/item.d.ts +2 -2
  71. package/dist/primitives/resource-list.cjs +6 -6
  72. package/dist/primitives/resource-list.js +5 -5
  73. package/dist/primitives/section-header.d.cts +1 -1
  74. package/dist/primitives/section-header.d.ts +1 -1
  75. package/dist/primitives/section.cjs +2 -2
  76. package/dist/primitives/section.js +3 -3
  77. package/dist/primitives/sheet.d.cts +1 -1
  78. package/dist/primitives/sheet.d.ts +1 -1
  79. package/dist/primitives/switch-field.cjs +2 -2
  80. package/dist/primitives/switch-field.js +3 -3
  81. package/dist/primitives/theme-selector.cjs +3 -3
  82. package/dist/primitives/theme-selector.js +4 -4
  83. package/dist/primitives/toggle-group.d.cts +2 -2
  84. package/dist/primitives/toggle-group.d.ts +2 -2
  85. package/dist/primitives/toggle.d.cts +2 -2
  86. package/dist/primitives/toggle.d.ts +2 -2
  87. package/dist/primitives/voice-dictation-button.cjs +5 -2
  88. package/dist/primitives/voice-dictation-button.d.cts +5 -9
  89. package/dist/primitives/voice-dictation-button.d.ts +5 -9
  90. package/dist/primitives/voice-dictation-button.js +4 -1
  91. package/dist/primitives/voice-session-controls.cjs +12 -0
  92. package/dist/primitives/voice-session-controls.d.cts +31 -0
  93. package/dist/primitives/voice-session-controls.d.ts +31 -0
  94. package/dist/primitives/voice-session-controls.js +12 -0
  95. package/package.json +51 -1
  96. package/dist/chunk-LKPM6DQP.cjs +0 -244
  97. package/dist/chunk-TI57GAQP.js +0 -244
  98. package/dist/{chunk-HREEZUKL.js → chunk-DILYV4FI.js} +3 -3
  99. package/dist/{chunk-MU4F3DSW.cjs → chunk-FARCLWLU.cjs} +2 -2
  100. package/dist/{chunk-CRTH5GJ5.cjs → chunk-MW67PHAQ.cjs} +3 -3
  101. package/dist/{chunk-KNKV25WS.js → chunk-UFFHGKI7.js} +3 -3
  102. package/dist/{chunk-HBRVOGLA.js → chunk-WHDHUYS6.js} +3 -3
  103. package/dist/{chunk-Z3JJSNR5.cjs → chunk-ZLEM2SAU.cjs} +2 -2
@@ -0,0 +1,412 @@
1
+ "use client";
2
+
3
+ // src/hooks/use-dictation.ts
4
+ import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
5
+ var DictationContext = createContext(null);
6
+ function DictationProvider({ transcribe, scopeKey, children }) {
7
+ const scope = useRef({ key: scopeKey, generation: 0 });
8
+ if (scope.current.key !== scopeKey) scope.current = { key: scopeKey, generation: scope.current.generation + 1 };
9
+ const generation = scope.current.generation;
10
+ const value = useMemo(() => ({ transcribe, scope, generation }), [transcribe, generation]);
11
+ return createElement(DictationContext.Provider, { value }, children);
12
+ }
13
+ var EMPTY = { state: "idle", transcript: "", interimTranscript: "", error: null, canRetry: false };
14
+ var MIME_TYPES = ["audio/webm;codecs=opus", "audio/mp4", "audio/ogg;codecs=opus", "audio/webm"];
15
+ var SILENCE = [];
16
+ function createAudioLevels() {
17
+ let bands = SILENCE;
18
+ const listeners = /* @__PURE__ */ new Set();
19
+ return {
20
+ subscribe: (listener) => {
21
+ listeners.add(listener);
22
+ return () => {
23
+ listeners.delete(listener);
24
+ };
25
+ },
26
+ getSnapshot: () => bands,
27
+ update: (next) => {
28
+ if (next.length === bands.length && next.every((value, index) => value === bands[index])) return;
29
+ bands = next;
30
+ listeners.forEach((listener) => listener());
31
+ }
32
+ };
33
+ }
34
+ function meterStream(stream, levels, active) {
35
+ let context;
36
+ let source;
37
+ let analyser;
38
+ let frame;
39
+ let stopped = false;
40
+ const stop = () => {
41
+ if (stopped) return;
42
+ stopped = true;
43
+ if (frame !== void 0) cancelAnimationFrame(frame);
44
+ try {
45
+ source?.disconnect();
46
+ } catch {
47
+ }
48
+ try {
49
+ analyser?.disconnect();
50
+ } catch {
51
+ }
52
+ try {
53
+ void context?.close().catch(() => {
54
+ });
55
+ } catch {
56
+ }
57
+ levels.update(SILENCE);
58
+ };
59
+ try {
60
+ const Constructor = window.AudioContext ?? window.webkitAudioContext;
61
+ if (!Constructor || typeof requestAnimationFrame !== "function") return stop;
62
+ context = new Constructor();
63
+ source = context.createMediaStreamSource(stream);
64
+ analyser = context.createAnalyser();
65
+ analyser.fftSize = 512;
66
+ analyser.smoothingTimeConstant = 0.7;
67
+ source.connect(analyser);
68
+ const bins = new Uint8Array(analyser.frequencyBinCount);
69
+ const binWidth = context.sampleRate / analyser.fftSize;
70
+ const highest = Math.min(8e3, context.sampleRate / 2);
71
+ let sampledAt = -Infinity;
72
+ const sample = (time) => {
73
+ if (stopped) return;
74
+ if (!active()) {
75
+ stop();
76
+ return;
77
+ }
78
+ try {
79
+ if (time - sampledAt >= 40) {
80
+ sampledAt = time;
81
+ analyser.getByteFrequencyData(bins);
82
+ const bands = Array.from({ length: 24 }, (_, index) => {
83
+ const from = Math.min(bins.length - 1, Math.floor(80 * (highest / 80) ** (index / 24) / binWidth));
84
+ const to = Math.min(bins.length, Math.max(from + 1, Math.ceil(80 * (highest / 80) ** ((index + 1) / 24) / binWidth)));
85
+ let total = 0;
86
+ for (let bin = from; bin < to; bin++) total += bins[bin];
87
+ return Math.round(total / (to - from) / 255 * 1e3) / 1e3;
88
+ });
89
+ levels.update(bands);
90
+ }
91
+ frame = requestAnimationFrame(sample);
92
+ } catch {
93
+ stop();
94
+ }
95
+ };
96
+ if (context.state === "suspended") void context.resume().catch(stop);
97
+ frame = requestAnimationFrame(sample);
98
+ } catch {
99
+ stop();
100
+ }
101
+ return stop;
102
+ }
103
+ function recognitionConstructor() {
104
+ if (typeof window === "undefined") return void 0;
105
+ const speech = window;
106
+ return speech.SpeechRecognition ?? speech.webkitSpeechRecognition;
107
+ }
108
+ function microphoneError(reason) {
109
+ const name = reason?.name;
110
+ if (name === "NotAllowedError" || name === "SecurityError") return "Microphone access is blocked. Allow it in your browser and try again.";
111
+ if (name === "NotFoundError" || name === "NotReadableError") return "The microphone is unavailable. Check your microphone and try again.";
112
+ return "Recording could not start. Check your microphone and try again.";
113
+ }
114
+ function recognitionError(code) {
115
+ if (code === "not-allowed" || code === "service-not-allowed") return microphoneError({ name: "NotAllowedError" });
116
+ if (code === "audio-capture") return microphoneError({ name: "NotFoundError" });
117
+ if (code === "no-speech") return "No speech was detected. Try again.";
118
+ return "Speech recognition stopped. Try again.";
119
+ }
120
+ function releaseMeter(session) {
121
+ session.stopMeter?.();
122
+ session.stopMeter = void 0;
123
+ }
124
+ function releaseTracks(session) {
125
+ releaseMeter(session);
126
+ session.stream?.getTracks().forEach((track) => track.stop());
127
+ session.stream = void 0;
128
+ }
129
+ function dispose(session) {
130
+ session.controller.abort();
131
+ if (session.nativeTimer) clearTimeout(session.nativeTimer);
132
+ const recorder = session.recorder;
133
+ if (recorder) {
134
+ recorder.ondataavailable = null;
135
+ recorder.onstop = null;
136
+ recorder.onerror = null;
137
+ if (recorder.state !== "inactive") {
138
+ try {
139
+ recorder.stop();
140
+ } catch {
141
+ }
142
+ }
143
+ }
144
+ const recognition = session.recognition;
145
+ if (recognition) {
146
+ recognition.onresult = null;
147
+ recognition.onerror = null;
148
+ recognition.onend = null;
149
+ try {
150
+ recognition.abort();
151
+ } catch {
152
+ }
153
+ }
154
+ releaseTracks(session);
155
+ session.resolveCapture(null);
156
+ session.resolveNativeEnd();
157
+ }
158
+ function useDictation({ lang = "en-US", continuous = true, onTranscript } = {}) {
159
+ const provider = useContext(DictationContext);
160
+ const transcribe = provider?.transcribe ?? null;
161
+ const scope = provider?.scope ?? null;
162
+ const scopeGeneration = provider?.generation ?? 0;
163
+ const [snapshot, setSnapshot] = useState(EMPTY);
164
+ const [supported, setSupported] = useState(false);
165
+ const current = useRef(null);
166
+ const mounted = useRef(true);
167
+ const callback = useRef(onTranscript);
168
+ const activeScope = useRef(scope);
169
+ activeScope.current = scope;
170
+ callback.current = onTranscript;
171
+ useEffect(() => {
172
+ setSupported(transcribe ? typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined" : typeof recognitionConstructor() === "function");
173
+ }, [transcribe]);
174
+ const cancel = useCallback(() => {
175
+ const session = current.current;
176
+ current.current = null;
177
+ if (session) dispose(session);
178
+ if (mounted.current) setSnapshot(EMPTY);
179
+ }, []);
180
+ useEffect(() => {
181
+ const session = current.current;
182
+ if (session && (session.scope !== scope || session.scopeGeneration !== scopeGeneration)) cancel();
183
+ }, [cancel, scope, scopeGeneration]);
184
+ useEffect(() => {
185
+ mounted.current = true;
186
+ return () => {
187
+ mounted.current = false;
188
+ const session = current.current;
189
+ current.current = null;
190
+ if (session) dispose(session);
191
+ };
192
+ }, []);
193
+ const isCurrent = useCallback((session) => mounted.current && current.current === session && session.scope === activeScope.current && session.scopeGeneration === (session.scope?.current.generation ?? 0) && !session.controller.signal.aborted, []);
194
+ const start = useCallback(async () => {
195
+ if (scope !== activeScope.current || scopeGeneration !== (scope?.current.generation ?? 0)) return;
196
+ if (current.current && isCurrent(current.current) && ["requesting", "recording", "transcribing"].includes(current.current.phase)) return;
197
+ cancel();
198
+ let resolveCapture;
199
+ let resolveNativeEnd;
200
+ const session = {
201
+ language: lang,
202
+ transcribe,
203
+ scope,
204
+ scopeGeneration,
205
+ controller: new AbortController(),
206
+ phase: "requesting",
207
+ chunks: [],
208
+ capture: new Promise((resolve) => {
209
+ resolveCapture = resolve;
210
+ }),
211
+ resolveCapture: (blob) => resolveCapture(blob),
212
+ nativeEnd: new Promise((resolve) => {
213
+ resolveNativeEnd = resolve;
214
+ }),
215
+ resolveNativeEnd: () => resolveNativeEnd(),
216
+ nativeEnded: false,
217
+ transcript: "",
218
+ interim: "",
219
+ notified: /* @__PURE__ */ new Set(),
220
+ audioLevels: transcribe ? createAudioLevels() : void 0
221
+ };
222
+ current.current = session;
223
+ setSnapshot({ ...EMPTY, state: "requesting" });
224
+ if (transcribe) {
225
+ try {
226
+ if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === "undefined") throw new Error("unsupported");
227
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
228
+ if (!isCurrent(session)) {
229
+ stream.getTracks().forEach((track) => track.stop());
230
+ return;
231
+ }
232
+ session.stream = stream;
233
+ const mimeType = MIME_TYPES.find((mime) => MediaRecorder.isTypeSupported?.(mime));
234
+ const recorder = mimeType ? new MediaRecorder(stream, { mimeType }) : new MediaRecorder(stream);
235
+ session.recorder = recorder;
236
+ recorder.ondataavailable = (event) => {
237
+ if (isCurrent(session) && event.data.size) session.chunks.push(event.data);
238
+ };
239
+ recorder.onstop = () => {
240
+ releaseTracks(session);
241
+ if (!isCurrent(session)) {
242
+ session.resolveCapture(null);
243
+ return;
244
+ }
245
+ const type = recorder.mimeType || session.chunks[0]?.type || mimeType || "audio/webm";
246
+ const blob = new Blob(session.chunks, { type });
247
+ session.blob = blob;
248
+ session.chunks = [];
249
+ session.resolveCapture(blob);
250
+ };
251
+ recorder.onerror = () => {
252
+ if (!isCurrent(session)) return;
253
+ releaseTracks(session);
254
+ session.phase = "error";
255
+ session.resolveCapture(null);
256
+ setSnapshot({ ...EMPTY, state: "error", error: "Recording was interrupted. Try again." });
257
+ };
258
+ recorder.start();
259
+ session.phase = "recording";
260
+ session.stopMeter = meterStream(stream, session.audioLevels, () => isCurrent(session) && session.phase === "recording");
261
+ setSnapshot({ ...EMPTY, state: "recording" });
262
+ } catch (error) {
263
+ releaseTracks(session);
264
+ if (!isCurrent(session)) return;
265
+ session.phase = "error";
266
+ setSnapshot({ ...EMPTY, state: "error", error: microphoneError(error) });
267
+ }
268
+ return;
269
+ }
270
+ try {
271
+ const Constructor = recognitionConstructor();
272
+ if (!Constructor) throw new Error("unsupported");
273
+ const recognition = new Constructor();
274
+ session.recognition = recognition;
275
+ recognition.lang = lang;
276
+ recognition.continuous = continuous;
277
+ recognition.interimResults = true;
278
+ recognition.onresult = (event) => {
279
+ if (!isCurrent(session) || session.phase === "error") return;
280
+ const final = [];
281
+ const interim = [];
282
+ const chunks = [];
283
+ for (let i = 0; i < event.results.length; i++) {
284
+ const result = event.results[i];
285
+ if (!result) continue;
286
+ const text = result[0].transcript.trim();
287
+ if (result.isFinal) {
288
+ final.push(text);
289
+ if (!session.notified.has(i)) {
290
+ session.notified.add(i);
291
+ if (text) chunks.push(text);
292
+ }
293
+ } else interim.push(text);
294
+ }
295
+ session.transcript = final.join(" ");
296
+ session.interim = interim.join(" ");
297
+ setSnapshot((previous) => ({ ...previous, transcript: session.transcript, interimTranscript: session.interim }));
298
+ if (chunks.length) callback.current?.(chunks.join(" "));
299
+ };
300
+ recognition.onend = () => {
301
+ session.nativeEnded = true;
302
+ session.resolveNativeEnd();
303
+ if (isCurrent(session) && session.phase === "recording") {
304
+ session.phase = "ready";
305
+ setSnapshot((previous) => ({ ...previous, state: "ready" }));
306
+ }
307
+ };
308
+ recognition.onerror = (event) => {
309
+ if (!isCurrent(session)) return;
310
+ session.phase = "error";
311
+ session.nativeEnded = true;
312
+ session.resolveNativeEnd();
313
+ setSnapshot((previous) => ({ ...previous, state: "error", error: recognitionError(event.error) }));
314
+ };
315
+ recognition.start();
316
+ session.phase = "recording";
317
+ setSnapshot({ ...EMPTY, state: "recording" });
318
+ } catch (error) {
319
+ if (!isCurrent(session)) return;
320
+ session.phase = "error";
321
+ setSnapshot({ ...EMPTY, state: "error", error: microphoneError(error) });
322
+ }
323
+ }, [cancel, continuous, isCurrent, lang, scope, scopeGeneration, transcribe]);
324
+ const finish = useCallback(async (session) => {
325
+ if (!isCurrent(session)) return null;
326
+ releaseMeter(session);
327
+ session.phase = "transcribing";
328
+ setSnapshot((previous) => ({ ...previous, state: "transcribing", error: null, canRetry: false }));
329
+ if (!session.transcribe) {
330
+ if (!session.nativeEnded) {
331
+ session.nativeTimer = setTimeout(() => {
332
+ try {
333
+ session.recognition?.abort();
334
+ } catch {
335
+ }
336
+ session.resolveNativeEnd();
337
+ }, 1e3);
338
+ try {
339
+ session.recognition?.stop();
340
+ } catch {
341
+ session.resolveNativeEnd();
342
+ }
343
+ await session.nativeEnd;
344
+ clearTimeout(session.nativeTimer);
345
+ }
346
+ if (!isCurrent(session) || session.phase === "error") return null;
347
+ const text2 = [session.transcript, session.interim].filter(Boolean).join(" ").trim();
348
+ session.phase = "ready";
349
+ setSnapshot({ ...EMPTY, state: "ready", transcript: text2 });
350
+ return text2;
351
+ }
352
+ let text;
353
+ try {
354
+ if (!session.blob) {
355
+ if (session.recorder?.state !== "inactive") session.recorder?.stop();
356
+ const blob = await session.capture;
357
+ if (!isCurrent(session) || !blob) return null;
358
+ session.blob = blob;
359
+ }
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();
362
+ if (!isCurrent(session)) return null;
363
+ if (!text) throw new Error("No speech was detected. Record again.");
364
+ } catch (error) {
365
+ if (!isCurrent(session)) return null;
366
+ releaseTracks(session);
367
+ session.phase = "error";
368
+ const message = error instanceof Error && error.message ? error.message : "Transcription failed. Try again.";
369
+ setSnapshot({ ...EMPTY, state: "error", error: message, canRetry: !!session.blob?.size });
370
+ return null;
371
+ }
372
+ session.transcript = text;
373
+ session.phase = "ready";
374
+ setSnapshot({ ...EMPTY, state: "ready", transcript: text });
375
+ callback.current?.(text);
376
+ return text;
377
+ }, [isCurrent]);
378
+ const stop = useCallback(() => {
379
+ const session = current.current;
380
+ if (!session) return Promise.resolve(null);
381
+ if (!isCurrent(session)) {
382
+ cancel();
383
+ return Promise.resolve(null);
384
+ }
385
+ if (session.phase === "requesting") {
386
+ cancel();
387
+ return Promise.resolve(null);
388
+ }
389
+ if (session.finish) return session.finish;
390
+ if (session.phase === "error") return Promise.resolve(null);
391
+ session.finish = finish(session);
392
+ return session.finish;
393
+ }, [cancel, finish, isCurrent]);
394
+ const retry = useCallback(() => {
395
+ const session = current.current;
396
+ if (session && !isCurrent(session)) {
397
+ cancel();
398
+ return Promise.resolve(null);
399
+ }
400
+ if (!session?.transcribe || !session.blob?.size) return Promise.resolve(null);
401
+ if (session.phase !== "error") return session.finish ?? Promise.resolve(null);
402
+ session.finish = finish(session);
403
+ return session.finish;
404
+ }, [cancel, finish, isCurrent]);
405
+ const visibleSnapshot = current.current && !isCurrent(current.current) ? EMPTY : snapshot;
406
+ return { ...visibleSnapshot, audioLevels: current.current && isCurrent(current.current) ? current.current.audioLevels : void 0, supported, start, stop, retry, cancel };
407
+ }
408
+
409
+ export {
410
+ DictationProvider,
411
+ useDictation
412
+ };
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  CopyButton
4
- } from "./chunk-HREEZUKL.js";
4
+ } from "./chunk-DILYV4FI.js";
5
5
  import {
6
6
  cn
7
7
  } from "./chunk-G6SRSWXB.js";
@@ -0,0 +1,221 @@
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 _chunkZZ7ICQR2cjs = require('./chunk-ZZ7ICQR2.cjs');
14
+
15
+ // src/primitives/voice-dictation-button.tsx
16
+
17
+
18
+
19
+
20
+
21
+
22
+ var _react = require('react');
23
+ var _reactdialog = require('@radix-ui/react-dialog'); var DialogPrimitive = _interopRequireWildcard(_reactdialog);
24
+ var _lucidereact = require('lucide-react');
25
+ var _jsxruntime = require('react/jsx-runtime');
26
+ function VoiceDictationButton({
27
+ onCommit,
28
+ onCancel,
29
+ title = "Dictate your message",
30
+ caption = "Recording",
31
+ lang = "en-US",
32
+ continuous = true,
33
+ className,
34
+ size = "sm",
35
+ showLabel = false,
36
+ children,
37
+ disabled = false,
38
+ visibility = "visible",
39
+ shrink = true
40
+ }) {
41
+ const [open, setOpen] = _react.useState.call(void 0, false);
42
+ const capture = _chunkZZ7ICQR2cjs.useDictation.call(void 0, { lang, continuous });
43
+ const { transcript, interimTranscript: interim, supported, error } = capture;
44
+ const generation = _react.useRef.call(void 0, 0);
45
+ const finishing = _react.useRef.call(void 0, false);
46
+ const previousFocus = _react.useRef.call(void 0, null);
47
+ const elapsed = useRecordingTime(capture.state);
48
+ const busy = capture.state === "requesting" || capture.state === "transcribing";
49
+ const openOverlay = _react.useCallback.call(void 0, () => {
50
+ previousFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
51
+ generation.current++;
52
+ finishing.current = false;
53
+ setOpen(true);
54
+ void capture.start();
55
+ }, [capture.start]);
56
+ const closeOverlay = _react.useCallback.call(void 0, () => {
57
+ generation.current++;
58
+ finishing.current = false;
59
+ capture.cancel();
60
+ setOpen(false);
61
+ }, [capture.cancel]);
62
+ const handleDone = _react.useCallback.call(void 0, async () => {
63
+ if (finishing.current || busy) return;
64
+ if (capture.state === "error" && !capture.canRetry) {
65
+ void capture.start();
66
+ return;
67
+ }
68
+ finishing.current = true;
69
+ const started = generation.current;
70
+ const final = await (capture.canRetry ? capture.retry() : capture.stop());
71
+ if (started !== generation.current) return;
72
+ finishing.current = false;
73
+ if (final === null) return;
74
+ closeOverlay();
75
+ if (final) onCommit(final);
76
+ else _optionalChain([onCancel, 'optionalCall', _ => _()]);
77
+ }, [busy, capture.canRetry, capture.retry, capture.start, capture.state, capture.stop, onCommit, onCancel, closeOverlay]);
78
+ const handleCancel = _react.useCallback.call(void 0, () => {
79
+ closeOverlay();
80
+ _optionalChain([onCancel, 'optionalCall', _2 => _2()]);
81
+ }, [closeOverlay, onCancel]);
82
+ _react.useEffect.call(void 0, () => {
83
+ if (open && capture.state === "idle") handleCancel();
84
+ }, [capture.state, handleCancel, open]);
85
+ _react.useEffect.call(void 0, () => {
86
+ return () => {
87
+ generation.current++;
88
+ };
89
+ }, []);
90
+ if (!supported) return null;
91
+ const displayTranscript = `${transcript}${interim ? (transcript ? " " : "") + interim : ""}`;
92
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
93
+ children ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
94
+ "button",
95
+ {
96
+ type: "button",
97
+ onClick: openOverlay,
98
+ disabled,
99
+ "aria-label": "Start dictation",
100
+ className: _chunkKWLSBTB2cjs.cn.call(void 0, visibility === "hidden" && "hidden", className),
101
+ children
102
+ }
103
+ ) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
104
+ _chunkJDEXZGIYcjs.Button,
105
+ {
106
+ type: "button",
107
+ onClick: openOverlay,
108
+ disabled,
109
+ variant: "outline",
110
+ size,
111
+ "aria-label": "Start dictation",
112
+ className: _chunkKWLSBTB2cjs.cn.call(void 0, "transition-colors", visibility === "hidden" && "hidden", !shrink && "shrink-0", className),
113
+ children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex items-center justify-center gap-2", children: [
114
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Mic, { className: "h-4 w-4" }),
115
+ showLabel && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: "Speak" })
116
+ ] })
117
+ }
118
+ ),
119
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
120
+ DialogPrimitive.Root,
121
+ {
122
+ open,
123
+ onOpenChange: (next) => {
124
+ if (!next) handleCancel();
125
+ },
126
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DialogPrimitive.Portal, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
127
+ DialogPrimitive.Content,
128
+ {
129
+ "aria-label": "Voice dictation",
130
+ "aria-labelledby": void 0,
131
+ "data-slot": "voice-dictation-panel",
132
+ "data-state": capture.state,
133
+ onCloseAutoFocus: (event) => {
134
+ event.preventDefault();
135
+ if (_optionalChain([previousFocus, 'access', _3 => _3.current, 'optionalAccess', _4 => _4.isConnected])) previousFocus.current.focus();
136
+ },
137
+ onKeyDown: (event) => {
138
+ if (event.key === "Enter" && !event.shiftKey && !(event.target instanceof HTMLElement && event.target.closest("button, input, textarea, select"))) {
139
+ event.preventDefault();
140
+ void handleDone();
141
+ }
142
+ },
143
+ className: "fixed bottom-4 left-1/2 z-[200] flex max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-sm -translate-x-1/2 flex-col gap-3 overflow-y-auto rounded-2xl border border-border bg-background p-4 text-foreground shadow-lg outline-hidden sm:bottom-6",
144
+ children: [
145
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex min-w-0 items-start justify-between gap-3", children: [
146
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "min-w-0 space-y-1", children: [
147
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DialogPrimitive.Title, { className: "text-sm font-medium", children: title || "Voice dictation" }),
148
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { role: "status", className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
149
+ 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" }),
150
+ capture.state === "transcribing" ? "Transcribing\u2026" : capture.state === "requesting" ? "Waiting for microphone\u2026" : error ? "Recording not saved" : capture.state === "ready" ? "Ready" : caption
151
+ ] })
152
+ ] }),
153
+ /* @__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: [
154
+ Math.floor(elapsed / 60).toString().padStart(2, "0"),
155
+ ":",
156
+ (elapsed % 60).toString().padStart(2, "0")
157
+ ] })
158
+ ] }),
159
+ capture.audioLevels && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DictationWaveform, { source: capture.audioLevels, state: capture.state }),
160
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DialogPrimitive.Description, { className: "text-xs text-muted-foreground", children: capture.state === "transcribing" ? "Converting your recording to text\u2026" : capture.state === "requesting" ? "Allow microphone access to start." : error ? capture.canRetry ? "Retry this take or record again." : "Check microphone access, then try again." : "Speak, then choose Done to add the text." }),
161
+ displayTranscript && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { className: "max-h-24 overflow-y-auto text-sm leading-relaxed", children: [
162
+ transcript,
163
+ interim && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "text-muted-foreground", children: [
164
+ transcript ? " " : "",
165
+ interim
166
+ ] })
167
+ ] }),
168
+ error && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { role: "alert", className: "text-xs text-destructive", children: error }),
169
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-wrap items-center justify-end gap-2", children: [
170
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { type: "button", variant: "ghost", size: "sm", className: "min-h-[44px]", onClick: handleCancel, children: "Cancel" }),
171
+ capture.canRetry && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { type: "button", variant: "outline", size: "sm", className: "min-h-[44px]", onClick: () => void capture.start(), children: "Record again" }),
172
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { type: "button", size: "sm", className: "min-h-[44px]", onClick: () => void handleDone(), disabled: busy, children: capture.state === "transcribing" ? "Transcribing\u2026" : capture.canRetry ? "Retry transcription" : error ? "Try again" : "Done" })
173
+ ] })
174
+ ]
175
+ }
176
+ ) })
177
+ }
178
+ )
179
+ ] });
180
+ }
181
+ var SILENCE = [];
182
+ var silence = () => SILENCE;
183
+ function DictationWaveform({ source, state }) {
184
+ const bands = _react.useSyncExternalStore.call(void 0, source.subscribe, source.getSnapshot, silence);
185
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
186
+ _chunkNKWHDI4Ccjs.AgentAudioVisualizerBar,
187
+ {
188
+ "aria-hidden": "true",
189
+ "data-slot": "dictation-waveform",
190
+ size: "icon",
191
+ barCount: 24,
192
+ state: state === "recording" ? "listening" : state === "transcribing" ? "thinking" : state === "requesting" ? "connecting" : "disconnected",
193
+ volumeBands: state === "recording" ? bands : void 0,
194
+ className: "h-9 w-full text-foreground"
195
+ }
196
+ );
197
+ }
198
+ function useRecordingTime(state) {
199
+ const [elapsed, setElapsed] = _react.useState.call(void 0, 0);
200
+ const startedAt = _react.useRef.call(void 0, null);
201
+ _react.useEffect.call(void 0, () => {
202
+ if (state !== "recording") {
203
+ if (state === "idle" || state === "requesting") setElapsed(0);
204
+ else if (startedAt.current !== null) setElapsed(Math.floor((performance.now() - startedAt.current) / 1e3));
205
+ startedAt.current = null;
206
+ return;
207
+ }
208
+ startedAt.current = performance.now();
209
+ const update = () => setElapsed(Math.floor((performance.now() - startedAt.current) / 1e3));
210
+ update();
211
+ const timer = setInterval(update, 1e3);
212
+ return () => {
213
+ clearInterval(timer);
214
+ };
215
+ }, [state]);
216
+ return elapsed;
217
+ }
218
+
219
+
220
+
221
+ exports.VoiceDictationButton = VoiceDictationButton;
@@ -7,16 +7,16 @@ import {
7
7
  } from "./chunk-EEHIMPD7.js";
8
8
  import {
9
9
  FilterBar
10
- } from "./chunk-KNKV25WS.js";
11
- import {
12
- Heading
13
- } from "./chunk-H2O6NMLZ.js";
10
+ } from "./chunk-UFFHGKI7.js";
14
11
  import {
15
12
  Tooltip,
16
13
  TooltipContent,
17
14
  TooltipProvider,
18
15
  TooltipTrigger
19
16
  } from "./chunk-ZRLUUZHK.js";
17
+ import {
18
+ Heading
19
+ } from "./chunk-H2O6NMLZ.js";
20
20
  import {
21
21
  BoardColumnComponent,
22
22
  MobileBoardView
@@ -30,12 +30,12 @@ import {
30
30
  import {
31
31
  Button
32
32
  } from "./chunk-W326GKML.js";
33
- import {
34
- useIsMobile
35
- } from "./chunk-PIU5SSUV.js";
36
33
  import {
37
34
  cn
38
35
  } from "./chunk-G6SRSWXB.js";
36
+ import {
37
+ useIsMobile
38
+ } from "./chunk-PIU5SSUV.js";
39
39
 
40
40
  // src/primitives/resource-list.tsx
41
41
  import { ChevronLeft, ChevronRight, Grid3X3, Kanban, List, Search, X } from "lucide-react";