@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
package/README.md CHANGED
@@ -7,6 +7,7 @@ dark modes.
7
7
 
8
8
  ## Install
9
9
 
10
+
10
11
  ```bash
11
12
  npm install @epicbagel/saas-kit
12
13
  ```
@@ -14,6 +15,75 @@ npm install @epicbagel/saas-kit
14
15
  Peer dependencies: `react` and `react-dom` (18 or 19), `lucide-react`
15
16
  (`>=0.400.0 <2`), `react-icons` (5) and `tailwindcss` (4.3 or later).
16
17
 
18
+ ## Live voice in Chat
19
+
20
+ Pass a controlled `voiceSession` to `Chat` or `ChatInput` to show start, mute,
21
+ end, connection and speaking controls alongside a separate dictation mic.
22
+ Dictation is disabled during a live call and the typed draft is preserved.
23
+ The active session appears as a compact horizontal strip above the text field:
24
+ microphone badge, audio bars, status, latest utterance, **Mute** and **End voice**.
25
+ It wraps its controls on narrow screens. Pass the latest user utterance as
26
+ `voiceSession.transcript`; this is plain preview text and never submits or replaces
27
+ the typed draft. `VoiceSessionControls` is also exported on its own.
28
+ The host owns audio, authentication, transcripts, retries and cleanup; the kit
29
+ does not import a voice provider. Stop and release audio tracks before setting
30
+ `state: "error"` or unmounting. Every label is replaceable through `labels`.
31
+
32
+ ```tsx
33
+ <Chat messages={messages} onSend={sendMessage} voiceSession={{
34
+ state: voiceState, muted, error, transcript: latestUserUtterance, audioLevels,
35
+ onStart: startVoice, onEnd: endVoice, onMutedChange: setMuted,
36
+ }} />
37
+ ```
38
+
39
+ ## API dictation
40
+
41
+ Wrap the application in `DictationProvider` to use an authenticated transcription
42
+ API for every `VoiceDictationButton`, including buttons nested in chat, forms or
43
+ another component library. A compact recorder shows the elapsed time and LiveKit
44
+ audio bars measured from the recording stream, without opening a second microphone.
45
+ It also works with hidden/programmatic triggers and inside another dialog.
46
+ The recorder runs until **Done**, shows
47
+ **Transcribing…**, then commits the returned text once. Failed requests keep the
48
+ take available for retry. Cancel stops microphone tracks, aborts the request and
49
+ ignores a late answer. The kit never persists audio or knows provider credentials.
50
+
51
+ ```tsx
52
+ import { DictationProvider } from "@epicbagel/saas-kit/primitives/dictation-provider";
53
+
54
+ <DictationProvider scopeKey={`${actorId}:${workspaceId}`} transcribe={async (audio, { language, signal }) => {
55
+ // The host sends the Blob to its authenticated API. Preserve audio.type
56
+ // and pass signal to fetch; keep provider keys on the server.
57
+ return transcribeRecording(audio, { language, signal });
58
+ }}>
59
+ <App />
60
+ </DictationProvider>
61
+ ```
62
+
63
+ Set `scopeKey` from the acting user and workspace (including impersonation, if
64
+ the app supports it). A changed scope discards the active take, stops recording
65
+ and aborts transcription without remounting the application. Responses from the
66
+ previous scope are ignored even before the cleanup effect runs; a failed take
67
+ cannot be retried under the new scope.
68
+
69
+ `useDictation` (`hooks/use-dictation`) exposes the same capture lifecycle for a
70
+ custom trigger such as press-and-hold: `start()`, `stop()`, `retry()`, `cancel()`,
71
+ `state`, `supported`, `transcript`, `interimTranscript`, `error` and `canRetry`.
72
+ For API recording, `audioLevels` exposes a subscribable snapshot of real microphone
73
+ levels. Subscribe only the waveform to keep audio sampling out of form renders;
74
+ native recognition has no audio-level source.
75
+ `stop()` and `retry()` return the completed text, or `null` after cancellation or
76
+ failure. Choose either that result or `onTranscript` to commit; using both would
77
+ duplicate the host action. The callback receives one complete API transcript,
78
+ or finalized chunks with native recognition. API recording requires HTTPS (or
79
+ localhost), microphone permission and MediaRecorder. It does not produce live
80
+ interim text; the live voice controls are a separate feature.
81
+
82
+ Without a provider, controls retain browser-native speech recognition. The
83
+ private ui-kit's `VoiceInput` uses this same provider and capture hook, and its
84
+ provider export is a shim of this exact context.
85
+
86
+
17
87
  ## Styles
18
88
 
19
89
  If the app has no Tailwind entry of its own, import the complete stylesheet:
@@ -104,7 +174,8 @@ public in 1.1.0, and these were the decisions about its props then:
104
174
  for the textarea's starting height; there is no class-name or `rows` prop.
105
175
  The textarea grows with its text (`field-sizing: content`).
106
176
  - **Compact mobile replies.** Opt into `Chat.mobileCompactComposer` to fold
107
- an active conversation's empty composer into one row below 768px. Focusing
177
+ an active conversation's empty composer into a short text row above its
178
+ controls below 768px, keeping all four actions clear of the text. Focusing
108
179
  its textarea expands it; leaving the composer folds it again only when
109
180
  there is no draft or staged file. The same textarea stays mounted, and
110
181
  its file menu and voice dialog keep an editing session open. New
@@ -268,3 +268,17 @@ The Apache License, Version 2.0:
268
268
 
269
269
  END OF TERMS AND CONDITIONS
270
270
  ```
271
+
272
+
273
+ ## LiveKit Agents UI
274
+
275
+ Source: https://github.com/livekit/components-js/tree/3f74806f1c8484b2eb2fd40773dd1be80760750c/packages/shadcn
276
+
277
+ `primitives/agent-audio-visualizer-bar` and
278
+ `hooks/use-agent-audio-visualizer-bar` are adapted from LiveKit's
279
+ `components/agents-ui/agent-audio-visualizer-bar.tsx` and
280
+ `hooks/agents-ui/use-agent-audio-visualizer-bar.ts` at that commit.
281
+ LiveKit contributors license them under Apache-2.0 (full license above).
282
+ The compiled output includes these adaptations. We removed the LiveKit React
283
+ track hook and accept host-supplied volume bands, clamp band values/counts,
284
+ and respect reduced motion and stop idle animation loops.
@@ -0,0 +1,71 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } 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
+ // src/hooks/use-agent-audio-visualizer-bar.ts
4
+ var _react = require('react');
5
+ function generateConnectingSequenceBar(columns) {
6
+ const seq = [];
7
+ for (let x = 0; x < columns; x++) {
8
+ seq.push([x, columns - 1 - x]);
9
+ }
10
+ return seq;
11
+ }
12
+ function generateListeningSequenceBar(columns) {
13
+ const center = Math.floor(columns / 2);
14
+ const noIndex = -1;
15
+ return [[center], [noIndex]];
16
+ }
17
+ function useAgentAudioVisualizerBarAnimator(state, columns, interval) {
18
+ const [index, setIndex] = _react.useState.call(void 0, 0);
19
+ const [sequence, setSequence] = _react.useState.call(void 0, [[]]);
20
+ _react.useEffect.call(void 0, () => {
21
+ if (state === "thinking") {
22
+ setSequence(generateListeningSequenceBar(columns));
23
+ } else if (state === "connecting" || state === "initializing") {
24
+ const sequence2 = [...generateConnectingSequenceBar(columns)];
25
+ setSequence(sequence2);
26
+ } else if (state === "listening") {
27
+ setSequence(generateListeningSequenceBar(columns));
28
+ } else if (state === void 0 || state === "speaking") {
29
+ setSequence([new Array(columns).fill(0).map((_, idx) => idx)]);
30
+ } else {
31
+ setSequence([[]]);
32
+ }
33
+ setIndex(0);
34
+ }, [state, columns]);
35
+ const animationFrameId = _react.useRef.call(void 0, null);
36
+ _react.useEffect.call(void 0, () => {
37
+ if (sequence.length < 2) return;
38
+ const preference = _optionalChain([window, 'access', _2 => _2.matchMedia, 'optionalCall', _3 => _3("(prefers-reduced-motion: reduce)")]);
39
+ const stop = () => {
40
+ if (animationFrameId.current !== null) cancelAnimationFrame(animationFrameId.current);
41
+ animationFrameId.current = null;
42
+ };
43
+ const start = () => {
44
+ stop();
45
+ if (_optionalChain([preference, 'optionalAccess', _4 => _4.matches])) {
46
+ setIndex(0);
47
+ return;
48
+ }
49
+ let startTime = performance.now();
50
+ const animate = (time) => {
51
+ if (time - startTime >= interval) {
52
+ setIndex((prev) => prev + 1);
53
+ startTime = time;
54
+ }
55
+ animationFrameId.current = requestAnimationFrame(animate);
56
+ };
57
+ animationFrameId.current = requestAnimationFrame(animate);
58
+ };
59
+ start();
60
+ _optionalChain([preference, 'optionalAccess', _5 => _5.addEventListener, 'call', _6 => _6("change", start)]);
61
+ return () => {
62
+ stop();
63
+ _optionalChain([preference, 'optionalAccess', _7 => _7.removeEventListener, 'call', _8 => _8("change", start)]);
64
+ };
65
+ }, [interval, columns, state, sequence.length]);
66
+ return _nullishCoalesce(sequence[index % sequence.length], () => ( []));
67
+ }
68
+
69
+
70
+
71
+ exports.useAgentAudioVisualizerBarAnimator = useAgentAudioVisualizerBarAnimator;
@@ -0,0 +1,158 @@
1
+ "use client";
2
+ import {
3
+ cn
4
+ } from "./chunk-G6SRSWXB.js";
5
+ import {
6
+ useAgentAudioVisualizerBarAnimator
7
+ } from "./chunk-GJ7S3L6R.js";
8
+
9
+ // src/primitives/agent-audio-visualizer-bar.tsx
10
+ import React, {
11
+ Children,
12
+ cloneElement,
13
+ isValidElement,
14
+ useMemo
15
+ } from "react";
16
+ import { cva } from "class-variance-authority";
17
+ import { jsx } from "react/jsx-runtime";
18
+ function normalizeVolumeBands(bands, count) {
19
+ if (bands.length === count) return bands;
20
+ if (bands.length > count) return bands.slice(0, count);
21
+ const lastValue = bands[bands.length - 1] ?? 0;
22
+ return [...bands, ...new Array(count - bands.length).fill(lastValue)];
23
+ }
24
+ function cloneSingleChild(children, props, key) {
25
+ return Children.map(children, (child) => {
26
+ if (isValidElement(child) && Children.only(children)) {
27
+ const childProps = child.props;
28
+ if (childProps.className) {
29
+ props ??= {};
30
+ props.className = cn(childProps.className, props.className);
31
+ props.style = {
32
+ ...childProps.style,
33
+ ...props.style
34
+ };
35
+ }
36
+ return cloneElement(child, { ...props, key: key ? String(key) : void 0 });
37
+ }
38
+ return child;
39
+ });
40
+ }
41
+ var AgentAudioVisualizerBarElementVariants = cva(
42
+ [
43
+ "rounded-full transition-colors duration-250 ease-linear motion-reduce:transition-none",
44
+ "bg-current/10 data-[lk-highlighted=true]:bg-current"
45
+ ],
46
+ {
47
+ variants: {
48
+ size: {
49
+ icon: "w-[4px] min-h-[4px]",
50
+ sm: "w-[8px] min-h-[8px]",
51
+ md: "w-[16px] min-h-[16px]",
52
+ lg: "w-[32px] min-h-[32px]",
53
+ xl: "w-[64px] min-h-[64px]"
54
+ }
55
+ },
56
+ defaultVariants: {
57
+ size: "md"
58
+ }
59
+ }
60
+ );
61
+ var AgentAudioVisualizerBarVariants = cva("relative flex items-center justify-center", {
62
+ variants: {
63
+ size: {
64
+ icon: "h-[24px] gap-[2px]",
65
+ sm: "h-[56px] gap-[4px]",
66
+ md: "h-[112px] gap-[8px]",
67
+ lg: "h-[224px] gap-[16px]",
68
+ xl: "h-[448px] gap-[32px]"
69
+ }
70
+ },
71
+ defaultVariants: {
72
+ size: "md"
73
+ }
74
+ });
75
+ function AgentAudioVisualizerBar({
76
+ size = "md",
77
+ state = "connecting",
78
+ color,
79
+ barCount,
80
+ volumeBands,
81
+ className,
82
+ children,
83
+ style,
84
+ ...props
85
+ }) {
86
+ const _barCount = useMemo(() => {
87
+ if (barCount && Number.isFinite(barCount)) {
88
+ return Math.max(1, Math.min(64, Math.floor(barCount)));
89
+ }
90
+ switch (size) {
91
+ case "icon":
92
+ case "sm":
93
+ return 3;
94
+ default:
95
+ return 5;
96
+ }
97
+ }, [barCount, size]);
98
+ const resolvedVolumeBands = normalizeVolumeBands(volumeBands ?? [], _barCount).map((value) => Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0);
99
+ const sequencerInterval = useMemo(() => {
100
+ switch (state) {
101
+ case "connecting":
102
+ return 2e3 / _barCount;
103
+ case "initializing":
104
+ return 2e3;
105
+ case "listening":
106
+ return 500;
107
+ case "thinking":
108
+ return 150;
109
+ default:
110
+ return 1e3;
111
+ }
112
+ }, [state, _barCount]);
113
+ const meteredListening = state === "listening" && volumeBands !== void 0;
114
+ const highlightedIndices = useAgentAudioVisualizerBarAnimator(
115
+ meteredListening ? "speaking" : state,
116
+ _barCount,
117
+ sequencerInterval
118
+ );
119
+ const bands = useMemo(
120
+ () => state === "speaking" || meteredListening ? resolvedVolumeBands : new Array(_barCount).fill(0),
121
+ [state, meteredListening, resolvedVolumeBands, _barCount]
122
+ );
123
+ if (children && Array.isArray(children)) {
124
+ throw new Error("AgentAudioVisualizerBar children must be a single element.");
125
+ }
126
+ return /* @__PURE__ */ jsx(
127
+ "div",
128
+ {
129
+ "data-lk-state": state,
130
+ style: { ...style, color },
131
+ className: cn(AgentAudioVisualizerBarVariants({ size }), className),
132
+ ...props,
133
+ children: bands.map(
134
+ (band, idx) => children ? /* @__PURE__ */ jsx(React.Fragment, { children: cloneSingleChild(children, {
135
+ "data-lk-index": idx,
136
+ "data-lk-highlighted": highlightedIndices.includes(idx),
137
+ style: { height: `${band * 100}%` }
138
+ }) }, idx) : /* @__PURE__ */ jsx(
139
+ "div",
140
+ {
141
+ "data-lk-index": idx,
142
+ "data-lk-highlighted": highlightedIndices.includes(idx),
143
+ style: { height: `${band * 100}%` },
144
+ className: cn(AgentAudioVisualizerBarElementVariants({ size }))
145
+ },
146
+ idx
147
+ )
148
+ )
149
+ }
150
+ );
151
+ }
152
+
153
+ export {
154
+ normalizeVolumeBands,
155
+ AgentAudioVisualizerBarElementVariants,
156
+ AgentAudioVisualizerBarVariants,
157
+ AgentAudioVisualizerBar
158
+ };
@@ -0,0 +1,153 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } 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
+ // src/primitives/voice-session-controls.tsx
13
+ var _react = require('react');
14
+ var _lucidereact = require('lucide-react');
15
+ var _jsxruntime = require('react/jsx-runtime');
16
+ var SILENCE = [];
17
+ var silence = () => SILENCE;
18
+ var noSubscription = () => () => {
19
+ };
20
+ var DEFAULT_LABELS = {
21
+ disconnected: "Voice chat",
22
+ connecting: "Connecting\u2026",
23
+ listening: "Listening\u2026",
24
+ thinking: "Thinking\u2026",
25
+ speaking: "Speaking",
26
+ reconnecting: "Reconnecting\u2026",
27
+ error: "Voice chat unavailable",
28
+ start: "Start voice chat",
29
+ end: "End voice chat",
30
+ mute: "Mute microphone",
31
+ unmute: "Unmute microphone",
32
+ muted: "Microphone muted",
33
+ retry: "Retry voice chat",
34
+ endShort: "End voice",
35
+ muteShort: "Mute",
36
+ unmuteShort: "Unmute",
37
+ transcriptPlaceholder: "Speak to continue the conversation."
38
+ };
39
+ function VoiceSessionControls({
40
+ state,
41
+ onStart,
42
+ onEnd,
43
+ muted = false,
44
+ onMutedChange,
45
+ disabled = false,
46
+ error,
47
+ transcript,
48
+ volumeBands,
49
+ audioLevels,
50
+ labels,
51
+ size = "icon",
52
+ className
53
+ }) {
54
+ const liveBands = _react.useSyncExternalStore.call(void 0, _nullishCoalesce(_optionalChain([audioLevels, 'optionalAccess', _ => _.subscribe]), () => ( noSubscription)), _nullishCoalesce(_optionalChain([audioLevels, 'optionalAccess', _2 => _2.getSnapshot]), () => ( silence)), silence);
55
+ const copy = { ...DEFAULT_LABELS, ...labels };
56
+ const active = state !== "disconnected" && state !== "error";
57
+ const waiting = state === "connecting" || state === "reconnecting";
58
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
59
+ "div",
60
+ {
61
+ "data-slot": "voice-session-controls",
62
+ "data-state": state,
63
+ className: _chunkKWLSBTB2cjs.cn.call(void 0, "min-w-0", active ? "@container/voice w-full" : "flex flex-wrap items-center gap-2", className),
64
+ children: [
65
+ active ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { "data-slot": "voice-session-strip", className: "flex w-full flex-wrap items-center gap-4 bg-primary/5 p-4 @2xl/voice:flex-nowrap @2xl/voice:px-5", children: [
66
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex min-w-0 flex-1 basis-full items-center gap-3 @2xl/voice:basis-auto @2xl/voice:gap-4", children: [
67
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { "aria-hidden": "true", className: _chunkKWLSBTB2cjs.cn.call(void 0,
68
+ "flex size-12 shrink-0 items-center justify-center rounded-full ring-8 ring-primary/5 @sm/voice:size-14",
69
+ muted ? "bg-muted text-muted-foreground" : "bg-primary text-primary-foreground"
70
+ ), children: muted ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.MicOff, { className: "size-5" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Mic, { className: "size-5" }) }),
71
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
72
+ _chunkNKWHDI4Ccjs.AgentAudioVisualizerBar,
73
+ {
74
+ "aria-hidden": "true",
75
+ size: "sm",
76
+ barCount: 5,
77
+ className: "shrink-0 text-primary [&>div]:w-1",
78
+ state: muted && state === "listening" ? "disconnected" : state === "reconnecting" ? "connecting" : state,
79
+ volumeBands: muted && state === "listening" ? SILENCE : _nullishCoalesce(volumeBands, () => ( liveBands))
80
+ }
81
+ ),
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
+ ] })
94
+ ] }),
95
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
96
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
97
+ _chunkJDEXZGIYcjs.Button,
98
+ {
99
+ type: "button",
100
+ variant: "outline",
101
+ size: "sm",
102
+ className: "min-h-[44px] shrink-0 rounded-xl bg-background px-3 text-foreground",
103
+ "aria-label": muted ? copy.unmute : copy.mute,
104
+ title: muted ? copy.unmute : copy.mute,
105
+ "aria-pressed": muted,
106
+ disabled: waiting,
107
+ onClick: () => onMutedChange(!muted),
108
+ children: [
109
+ muted ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Mic, { "aria-hidden": "true" }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.MicOff, { "aria-hidden": "true" }),
110
+ muted ? copy.unmuteShort : copy.muteShort
111
+ ]
112
+ }
113
+ ),
114
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
115
+ _chunkJDEXZGIYcjs.Button,
116
+ {
117
+ type: "button",
118
+ variant: "outline",
119
+ size: "sm",
120
+ className: "min-h-[44px] shrink-0 rounded-xl bg-background px-3 text-foreground",
121
+ "aria-label": copy.end,
122
+ title: copy.end,
123
+ onClick: onEnd,
124
+ children: [
125
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Square, { "aria-hidden": "true", className: "fill-current" }),
126
+ copy.endShort
127
+ ]
128
+ }
129
+ )
130
+ ] })
131
+ ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
132
+ _chunkJDEXZGIYcjs.Button,
133
+ {
134
+ type: "button",
135
+ size: "icon",
136
+ shape: "circle",
137
+ disabled,
138
+ onClick: onStart,
139
+ "aria-label": state === "error" ? copy.retry : copy.start,
140
+ title: state === "error" ? copy.retry : copy.start,
141
+ className: _chunkKWLSBTB2cjs.cn.call(void 0, "min-h-[44px] min-w-[44px] shrink-0 rounded-full", size === "hero" && "size-24 [&_svg]:size-7"),
142
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.AudioLines, { "aria-hidden": "true" })
143
+ }
144
+ ),
145
+ state === "error" && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { role: "alert", className: "basis-full text-xs text-destructive", children: error || copy.error })
146
+ ]
147
+ }
148
+ );
149
+ }
150
+
151
+
152
+
153
+ exports.VoiceSessionControls = VoiceSessionControls;
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
2
2
 
3
3
 
4
- var _chunkMU4F3DSWcjs = require('./chunk-MU4F3DSW.cjs');
4
+ var _chunkFARCLWLUcjs = require('./chunk-FARCLWLU.cjs');
5
5
 
6
6
 
7
7
  var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
@@ -38,7 +38,7 @@ function CodeBlock({
38
38
  }
39
39
  ),
40
40
  copy ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
41
- _chunkMU4F3DSWcjs.CopyButton,
41
+ _chunkFARCLWLUcjs.CopyButton,
42
42
  {
43
43
  text: copyText,
44
44
  label: copyLabel,
@@ -7,16 +7,16 @@ var _chunkEUFV25KLcjs = require('./chunk-EUFV25KL.cjs');
7
7
  var _chunk7YCKII7Gcjs = require('./chunk-7YCKII7G.cjs');
8
8
 
9
9
 
10
- var _chunkCRTH5GJ5cjs = require('./chunk-CRTH5GJ5.cjs');
10
+ var _chunkMW67PHAQcjs = require('./chunk-MW67PHAQ.cjs');
11
11
 
12
12
 
13
- var _chunkLELBCBDTcjs = require('./chunk-LELBCBDT.cjs');
14
13
 
15
14
 
16
15
 
16
+ var _chunkU2NES7VRcjs = require('./chunk-U2NES7VR.cjs');
17
17
 
18
18
 
19
- var _chunkU2NES7VRcjs = require('./chunk-U2NES7VR.cjs');
19
+ var _chunkLELBCBDTcjs = require('./chunk-LELBCBDT.cjs');
20
20
 
21
21
 
22
22
 
@@ -32,10 +32,10 @@ var _chunkPQUSJEXLcjs = require('./chunk-PQUSJEXL.cjs');
32
32
  var _chunkJDEXZGIYcjs = require('./chunk-JDEXZGIY.cjs');
33
33
 
34
34
 
35
- var _chunkR35FT53Gcjs = require('./chunk-R35FT53G.cjs');
35
+ var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
36
36
 
37
37
 
38
- var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
38
+ var _chunkR35FT53Gcjs = require('./chunk-R35FT53G.cjs');
39
39
 
40
40
  // src/primitives/resource-list.tsx
41
41
  var _lucidereact = require('lucide-react');
@@ -268,7 +268,7 @@ function ResourceList({
268
268
  const controlsVisible = !(hideControlsWhenEmpty && !loading && !error && _optionalChain([data, 'optionalAccess', _5 => _5.length]) === 0);
269
269
  const filterOnQuickRow = filterBarVisible && !toolbarHasOtherControls && hasQuickFilters;
270
270
  const filterBarNode = filterBarVisible ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
271
- _chunkCRTH5GJ5cjs.FilterBar,
271
+ _chunkMW67PHAQcjs.FilterBar,
272
272
  {
273
273
  filters: filterSchema,
274
274
  onFiltersChange,
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ // src/hooks/use-agent-audio-visualizer-bar.ts
4
+ import { useEffect, useRef, useState } from "react";
5
+ function generateConnectingSequenceBar(columns) {
6
+ const seq = [];
7
+ for (let x = 0; x < columns; x++) {
8
+ seq.push([x, columns - 1 - x]);
9
+ }
10
+ return seq;
11
+ }
12
+ function generateListeningSequenceBar(columns) {
13
+ const center = Math.floor(columns / 2);
14
+ const noIndex = -1;
15
+ return [[center], [noIndex]];
16
+ }
17
+ function useAgentAudioVisualizerBarAnimator(state, columns, interval) {
18
+ const [index, setIndex] = useState(0);
19
+ const [sequence, setSequence] = useState([[]]);
20
+ useEffect(() => {
21
+ if (state === "thinking") {
22
+ setSequence(generateListeningSequenceBar(columns));
23
+ } else if (state === "connecting" || state === "initializing") {
24
+ const sequence2 = [...generateConnectingSequenceBar(columns)];
25
+ setSequence(sequence2);
26
+ } else if (state === "listening") {
27
+ setSequence(generateListeningSequenceBar(columns));
28
+ } else if (state === void 0 || state === "speaking") {
29
+ setSequence([new Array(columns).fill(0).map((_, idx) => idx)]);
30
+ } else {
31
+ setSequence([[]]);
32
+ }
33
+ setIndex(0);
34
+ }, [state, columns]);
35
+ const animationFrameId = useRef(null);
36
+ useEffect(() => {
37
+ if (sequence.length < 2) return;
38
+ const preference = window.matchMedia?.("(prefers-reduced-motion: reduce)");
39
+ const stop = () => {
40
+ if (animationFrameId.current !== null) cancelAnimationFrame(animationFrameId.current);
41
+ animationFrameId.current = null;
42
+ };
43
+ const start = () => {
44
+ stop();
45
+ if (preference?.matches) {
46
+ setIndex(0);
47
+ return;
48
+ }
49
+ let startTime = performance.now();
50
+ const animate = (time) => {
51
+ if (time - startTime >= interval) {
52
+ setIndex((prev) => prev + 1);
53
+ startTime = time;
54
+ }
55
+ animationFrameId.current = requestAnimationFrame(animate);
56
+ };
57
+ animationFrameId.current = requestAnimationFrame(animate);
58
+ };
59
+ start();
60
+ preference?.addEventListener("change", start);
61
+ return () => {
62
+ stop();
63
+ preference?.removeEventListener("change", start);
64
+ };
65
+ }, [interval, columns, state, sequence.length]);
66
+ return sequence[index % sequence.length] ?? [];
67
+ }
68
+
69
+ export {
70
+ useAgentAudioVisualizerBarAnimator
71
+ };