@epicbagel/saas-kit 1.6.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -1
- package/THIRD_PARTY_NOTICES.md +14 -0
- package/dist/chunk-2BSFYU22.cjs +71 -0
- package/dist/{chunk-GTVSRRE3.cjs → chunk-BGCNGYPL.cjs} +2 -2
- package/dist/{chunk-QTWJQQDH.cjs → chunk-FBVZRRA4.cjs} +6 -6
- package/dist/chunk-GJ7S3L6R.js +71 -0
- package/dist/{chunk-LKPM6DQP.cjs → chunk-HTCW6AEH.cjs} +51 -72
- package/dist/{chunk-F3B4M6DU.js → chunk-HTPPNW74.js} +1 -1
- package/dist/chunk-KUG4ONYC.js +127 -0
- package/dist/{chunk-TI57GAQP.js → chunk-KWV5KTOA.js} +48 -69
- package/dist/{chunk-JN6JOSZG.js → chunk-LQPJGZIE.js} +7 -7
- package/dist/chunk-M2ZNC3TK.js +316 -0
- package/dist/chunk-O6F3LVZY.cjs +316 -0
- package/dist/chunk-UW67GSZ4.cjs +127 -0
- package/dist/chunk-VBKJXNEL.js +157 -0
- package/dist/chunk-XB2D4NQK.cjs +157 -0
- package/dist/hooks/use-agent-audio-visualizer-bar.cjs +8 -0
- package/dist/hooks/use-agent-audio-visualizer-bar.d.cts +4 -0
- package/dist/hooks/use-agent-audio-visualizer-bar.d.ts +4 -0
- package/dist/hooks/use-agent-audio-visualizer-bar.js +8 -0
- package/dist/hooks/use-dictation.cjs +10 -0
- package/dist/hooks/use-dictation.d.cts +56 -0
- package/dist/hooks/use-dictation.d.ts +56 -0
- package/dist/hooks/use-dictation.js +10 -0
- package/dist/primitives/activity-feed.cjs +3 -3
- package/dist/primitives/activity-feed.js +4 -4
- package/dist/primitives/agent-audio-visualizer-bar.cjs +16 -0
- package/dist/primitives/agent-audio-visualizer-bar.d.cts +75 -0
- package/dist/primitives/agent-audio-visualizer-bar.d.ts +75 -0
- package/dist/primitives/agent-audio-visualizer-bar.js +16 -0
- package/dist/primitives/archive-confirmation-dialog.cjs +2 -2
- package/dist/primitives/archive-confirmation-dialog.js +3 -3
- package/dist/primitives/board-view.cjs +6 -6
- package/dist/primitives/board-view.js +5 -5
- package/dist/primitives/chat.cjs +127 -48
- package/dist/primitives/chat.d.cts +25 -3
- package/dist/primitives/chat.d.ts +25 -3
- package/dist/primitives/chat.js +122 -43
- package/dist/primitives/code-block.cjs +4 -4
- package/dist/primitives/code-block.js +3 -3
- package/dist/primitives/connection.cjs +6 -6
- package/dist/primitives/connection.js +7 -7
- package/dist/primitives/copy-button.cjs +3 -3
- package/dist/primitives/copy-button.js +2 -2
- package/dist/primitives/dictation-provider.cjs +8 -0
- package/dist/primitives/dictation-provider.d.cts +2 -0
- package/dist/primitives/dictation-provider.d.ts +2 -0
- package/dist/primitives/dictation-provider.js +8 -0
- package/dist/primitives/editable-list.cjs +1 -1
- package/dist/primitives/editable-list.js +1 -1
- package/dist/primitives/file-dropzone.cjs +3 -3
- package/dist/primitives/file-dropzone.js +2 -2
- package/dist/primitives/file-preview.cjs +6 -6
- package/dist/primitives/file-preview.js +5 -5
- package/dist/primitives/filter-bar.cjs +4 -4
- package/dist/primitives/filter-bar.js +3 -3
- package/dist/primitives/input-group.d.cts +1 -1
- package/dist/primitives/input-group.d.ts +1 -1
- package/dist/primitives/resource-list.cjs +6 -6
- package/dist/primitives/resource-list.js +5 -5
- package/dist/primitives/section.cjs +2 -2
- package/dist/primitives/section.js +3 -3
- package/dist/primitives/sheet.d.cts +1 -1
- package/dist/primitives/sheet.d.ts +1 -1
- package/dist/primitives/switch-field.cjs +2 -2
- package/dist/primitives/switch-field.js +3 -3
- package/dist/primitives/theme-selector.cjs +3 -3
- package/dist/primitives/theme-selector.js +4 -4
- package/dist/primitives/voice-dictation-button.cjs +3 -2
- package/dist/primitives/voice-dictation-button.js +2 -1
- package/dist/primitives/voice-session-controls.cjs +12 -0
- package/dist/primitives/voice-session-controls.d.cts +29 -0
- package/dist/primitives/voice-session-controls.d.ts +29 -0
- package/dist/primitives/voice-session-controls.js +12 -0
- package/package.json +51 -1
- package/dist/{chunk-HREEZUKL.js → chunk-DILYV4FI.js} +3 -3
- package/dist/{chunk-MU4F3DSW.cjs → chunk-FARCLWLU.cjs} +2 -2
- package/dist/{chunk-CRTH5GJ5.cjs → chunk-MW67PHAQ.cjs} +3 -3
- package/dist/{chunk-KNKV25WS.js → chunk-UFFHGKI7.js} +3 -3
- package/dist/{chunk-HBRVOGLA.js → chunk-WHDHUYS6.js} +3 -3
- 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,65 @@ 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
|
+
`VoiceSessionControls` is also exported on its own.
|
|
24
|
+
The host owns audio, authentication, transcripts, retries and cleanup; the kit
|
|
25
|
+
does not import a voice provider. Stop and release audio tracks before setting
|
|
26
|
+
`state: "error"` or unmounting. Every label is replaceable through `labels`.
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<Chat messages={messages} onSend={sendMessage} voiceSession={{
|
|
30
|
+
state: voiceState, muted, error,
|
|
31
|
+
onStart: startVoice, onEnd: endVoice, onMutedChange: setMuted,
|
|
32
|
+
}} />
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## API dictation
|
|
36
|
+
|
|
37
|
+
Wrap the application in `DictationProvider` to use an authenticated transcription
|
|
38
|
+
API for every `VoiceDictationButton`, including buttons nested in chat, forms or
|
|
39
|
+
another component library. The existing dialog records until **Done**, shows
|
|
40
|
+
**Transcribing…**, then commits the returned text once. Failed requests keep the
|
|
41
|
+
take available for retry. Cancel stops microphone tracks, aborts the request and
|
|
42
|
+
ignores a late answer. The kit never persists audio or knows provider credentials.
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import { DictationProvider } from "@epicbagel/saas-kit/primitives/dictation-provider";
|
|
46
|
+
|
|
47
|
+
<DictationProvider scopeKey={`${actorId}:${workspaceId}`} transcribe={async (audio, { language, signal }) => {
|
|
48
|
+
// The host sends the Blob to its authenticated API. Preserve audio.type
|
|
49
|
+
// and pass signal to fetch; keep provider keys on the server.
|
|
50
|
+
return transcribeRecording(audio, { language, signal });
|
|
51
|
+
}}>
|
|
52
|
+
<App />
|
|
53
|
+
</DictationProvider>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Set `scopeKey` from the acting user and workspace (including impersonation, if
|
|
57
|
+
the app supports it). A changed scope discards the active take, stops recording
|
|
58
|
+
and aborts transcription without remounting the application. Responses from the
|
|
59
|
+
previous scope are ignored even before the cleanup effect runs; a failed take
|
|
60
|
+
cannot be retried under the new scope.
|
|
61
|
+
|
|
62
|
+
`useDictation` (`hooks/use-dictation`) exposes the same capture lifecycle for a
|
|
63
|
+
custom trigger such as press-and-hold: `start()`, `stop()`, `retry()`, `cancel()`,
|
|
64
|
+
`state`, `supported`, `transcript`, `interimTranscript`, `error` and `canRetry`.
|
|
65
|
+
`stop()` and `retry()` return the completed text, or `null` after cancellation or
|
|
66
|
+
failure. Choose either that result or `onTranscript` to commit; using both would
|
|
67
|
+
duplicate the host action. The callback receives one complete API transcript,
|
|
68
|
+
or finalized chunks with native recognition. API recording requires HTTPS (or
|
|
69
|
+
localhost), microphone permission and MediaRecorder. It does not produce live
|
|
70
|
+
interim text; the live voice controls are a separate feature.
|
|
71
|
+
|
|
72
|
+
Without a provider, controls retain browser-native speech recognition. The
|
|
73
|
+
private ui-kit's `VoiceInput` uses this same provider and capture hook, and its
|
|
74
|
+
provider export is a shim of this exact context.
|
|
75
|
+
|
|
76
|
+
|
|
17
77
|
## Styles
|
|
18
78
|
|
|
19
79
|
If the app has no Tailwind entry of its own, import the complete stylesheet:
|
|
@@ -94,7 +154,8 @@ public in 1.1.0, and these were the decisions about its props then:
|
|
|
94
154
|
("Thinking…"), `scrollButtonLabel` ("Scroll to latest message"),
|
|
95
155
|
`uploadingLabel` ("Uploading…"), `voiceHeroLabel` ("Tap to speak"),
|
|
96
156
|
`voiceHeroSwitchLabel` ("or type instead"), `examplePromptsLabel` ("Try"),
|
|
97
|
-
`examplePromptsLoadingLabel` ("Loading suggestions…"),
|
|
157
|
+
`examplePromptsLoadingLabel` ("Loading suggestions…"),
|
|
158
|
+
`replySuggestionsLabel` ("Suggested replies"), `addFilesLabel`,
|
|
98
159
|
`placeholder` and `activePlaceholder`. The defaults are plain English with
|
|
99
160
|
no product voice, and the empty state has no title unless you pass
|
|
100
161
|
`emptyTitle`. The icon buttons' accessible names ("Send", "Add to
|
|
@@ -114,6 +175,15 @@ public in 1.1.0, and these were the decisions about its props then:
|
|
|
114
175
|
- **Example prompts.** `examplePromptsVariant` is `chips` (the default: chips
|
|
115
176
|
that wrap onto as many lines as they need), `row` (one line that scrolls
|
|
116
177
|
sideways) or `cards` (a two-column grid).
|
|
178
|
+
- **Reply suggestions (1.7.0).** `replySuggestions` are the replies the
|
|
179
|
+
conversation offers next. They show once the conversation has started, at
|
|
180
|
+
the composer, directly above the input, as the example prompts' chips (one
|
|
181
|
+
implementation draws both), and wrap rather than cover the input on a
|
|
182
|
+
phone. `replySuggestionsLabel` ("Suggested replies") names the group.
|
|
183
|
+
Picking a suggestion of either kind sends only its words, `onSend(text,
|
|
184
|
+
[])`: the draft and staged files stay, and a failure is reported without
|
|
185
|
+
touching them. Suggestions are disabled while `pending`; pass none once
|
|
186
|
+
they no longer apply.
|
|
117
187
|
- **Failed sends.** When `onSend` rejects, Chat puts the text back in the
|
|
118
188
|
composer and calls `onSendError`; without one it reports the rejection
|
|
119
189
|
through `reportError`, so a failure is never silent.
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -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;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
35
|
+
var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
var
|
|
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
|
-
|
|
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
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
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
|
|
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
2
|
|
|
3
3
|
|
|
4
4
|
var _chunkJDEXZGIYcjs = require('./chunk-JDEXZGIY.cjs');
|
|
@@ -6,6 +6,9 @@ var _chunkJDEXZGIYcjs = require('./chunk-JDEXZGIY.cjs');
|
|
|
6
6
|
|
|
7
7
|
var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
var _chunkO6F3LVZYcjs = require('./chunk-O6F3LVZY.cjs');
|
|
11
|
+
|
|
9
12
|
// src/primitives/voice-dictation-button.tsx
|
|
10
13
|
|
|
11
14
|
|
|
@@ -31,87 +34,61 @@ function VoiceDictationButton({
|
|
|
31
34
|
visibility = "visible",
|
|
32
35
|
shrink = true
|
|
33
36
|
}) {
|
|
34
|
-
const [supported, setSupported] = _react.useState.call(void 0, false);
|
|
35
37
|
const [open, setOpen] = _react.useState.call(void 0, false);
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
const w = window;
|
|
42
|
-
const Ctor = _nullishCoalesce(w.SpeechRecognition, () => ( w.webkitSpeechRecognition));
|
|
43
|
-
setSupported(typeof Ctor === "function");
|
|
44
|
-
}, []);
|
|
45
|
-
const stop = _react.useCallback.call(void 0, () => {
|
|
46
|
-
_optionalChain([recognitionRef, 'access', _ => _.current, 'optionalAccess', _2 => _2.stop, 'call', _3 => _3()]);
|
|
47
|
-
recognitionRef.current = null;
|
|
48
|
-
}, []);
|
|
49
|
-
const startRecognition = _react.useCallback.call(void 0, () => {
|
|
50
|
-
const w = window;
|
|
51
|
-
const Ctor = _nullishCoalesce(w.SpeechRecognition, () => ( w.webkitSpeechRecognition));
|
|
52
|
-
if (!Ctor) return;
|
|
53
|
-
const rec = new Ctor();
|
|
54
|
-
rec.continuous = continuous;
|
|
55
|
-
rec.interimResults = true;
|
|
56
|
-
rec.lang = lang;
|
|
57
|
-
rec.onresult = (event) => {
|
|
58
|
-
let finalChunk = "";
|
|
59
|
-
let interimChunk = "";
|
|
60
|
-
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
61
|
-
const r = event.results[i];
|
|
62
|
-
if (!r) continue;
|
|
63
|
-
if (r.isFinal) finalChunk += r[0].transcript;
|
|
64
|
-
else interimChunk += r[0].transcript;
|
|
65
|
-
}
|
|
66
|
-
if (finalChunk) {
|
|
67
|
-
setTranscript((prev) => prev ? prev + " " + finalChunk : finalChunk);
|
|
68
|
-
setInterim("");
|
|
69
|
-
} else if (interimChunk) {
|
|
70
|
-
setInterim(interimChunk);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
rec.onerror = () => {
|
|
74
|
-
setErrored(true);
|
|
75
|
-
};
|
|
76
|
-
rec.onend = () => {
|
|
77
|
-
recognitionRef.current = null;
|
|
78
|
-
};
|
|
79
|
-
recognitionRef.current = rec;
|
|
80
|
-
setErrored(false);
|
|
81
|
-
rec.start();
|
|
82
|
-
}, [continuous, lang]);
|
|
38
|
+
const capture = _chunkO6F3LVZYcjs.useDictation.call(void 0, { lang, continuous });
|
|
39
|
+
const { transcript, interimTranscript: interim, supported, error } = capture;
|
|
40
|
+
const generation = _react.useRef.call(void 0, 0);
|
|
41
|
+
const finishing = _react.useRef.call(void 0, false);
|
|
42
|
+
const busy = capture.state === "requesting" || capture.state === "transcribing";
|
|
83
43
|
const openOverlay = _react.useCallback.call(void 0, () => {
|
|
84
|
-
|
|
85
|
-
|
|
44
|
+
generation.current++;
|
|
45
|
+
finishing.current = false;
|
|
86
46
|
setOpen(true);
|
|
87
|
-
|
|
88
|
-
}, [
|
|
47
|
+
void capture.start();
|
|
48
|
+
}, [capture.start]);
|
|
89
49
|
const closeOverlay = _react.useCallback.call(void 0, () => {
|
|
90
|
-
|
|
50
|
+
generation.current++;
|
|
51
|
+
finishing.current = false;
|
|
52
|
+
capture.cancel();
|
|
91
53
|
setOpen(false);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
54
|
+
}, [capture.cancel]);
|
|
55
|
+
const handleDone = _react.useCallback.call(void 0, async () => {
|
|
56
|
+
if (finishing.current || busy) return;
|
|
57
|
+
if (capture.state === "error" && !capture.canRetry) {
|
|
58
|
+
void capture.start();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
finishing.current = true;
|
|
62
|
+
const started = generation.current;
|
|
63
|
+
const final = await (capture.canRetry ? capture.retry() : capture.stop());
|
|
64
|
+
if (started !== generation.current) return;
|
|
65
|
+
finishing.current = false;
|
|
66
|
+
if (final === null) return;
|
|
96
67
|
closeOverlay();
|
|
97
68
|
if (final) onCommit(final);
|
|
98
|
-
else _optionalChain([onCancel, 'optionalCall',
|
|
99
|
-
}, [
|
|
69
|
+
else _optionalChain([onCancel, 'optionalCall', _ => _()]);
|
|
70
|
+
}, [busy, capture.canRetry, capture.retry, capture.start, capture.state, capture.stop, onCommit, onCancel, closeOverlay]);
|
|
100
71
|
const handleCancel = _react.useCallback.call(void 0, () => {
|
|
101
72
|
closeOverlay();
|
|
102
|
-
_optionalChain([onCancel, 'optionalCall',
|
|
73
|
+
_optionalChain([onCancel, 'optionalCall', _2 => _2()]);
|
|
103
74
|
}, [closeOverlay, onCancel]);
|
|
75
|
+
_react.useEffect.call(void 0, () => {
|
|
76
|
+
if (open && capture.state === "idle") handleCancel();
|
|
77
|
+
}, [capture.state, handleCancel, open]);
|
|
104
78
|
_react.useEffect.call(void 0, () => {
|
|
105
79
|
if (!open) return;
|
|
106
80
|
const onKey = (e) => {
|
|
107
|
-
if (e.key === "Enter" && !e.shiftKey)
|
|
81
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
void handleDone();
|
|
84
|
+
}
|
|
108
85
|
};
|
|
109
86
|
document.addEventListener("keydown", onKey);
|
|
110
87
|
return () => document.removeEventListener("keydown", onKey);
|
|
111
88
|
}, [open, handleDone]);
|
|
112
89
|
_react.useEffect.call(void 0, () => {
|
|
113
90
|
return () => {
|
|
114
|
-
|
|
91
|
+
generation.current++;
|
|
115
92
|
};
|
|
116
93
|
}, []);
|
|
117
94
|
if (!supported) return null;
|
|
@@ -148,7 +125,7 @@ function VoiceDictationButton({
|
|
|
148
125
|
{
|
|
149
126
|
open,
|
|
150
127
|
onOpenChange: (next) => {
|
|
151
|
-
if (!next)
|
|
128
|
+
if (!next) handleCancel();
|
|
152
129
|
},
|
|
153
130
|
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DialogPrimitive.Portal, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
154
131
|
DialogPrimitive.Content,
|
|
@@ -161,9 +138,10 @@ function VoiceDictationButton({
|
|
|
161
138
|
className: "fixed inset-0 z-[200] flex flex-col bg-background outline-hidden md:items-center md:justify-center md:bg-black/40 md:backdrop-blur-sm",
|
|
162
139
|
children: [
|
|
163
140
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, DialogPrimitive.Title, { className: "sr-only", children: "Voice dictation" }),
|
|
141
|
+
/* @__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." }),
|
|
164
142
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex-1 md:flex-initial w-full md:max-w-lg md:rounded-xl bg-background flex flex-col md:max-h-[80vh] md:shadow-2xl md:border md:border-border", children: [
|
|
165
143
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center justify-between px-4 py-3 border-b border-border md:rounded-t-xl", children: [
|
|
166
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "font-mono text-xs uppercase tracking-wider text-muted-foreground", children: caption }),
|
|
144
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { role: "status", className: "font-mono text-xs uppercase tracking-wider text-muted-foreground", children: capture.state === "transcribing" ? "transcribing" : capture.state === "requesting" ? "microphone" : caption }),
|
|
167
145
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
168
146
|
"button",
|
|
169
147
|
{
|
|
@@ -183,7 +161,7 @@ function VoiceDictationButton({
|
|
|
183
161
|
"aria-hidden": true,
|
|
184
162
|
className: _chunkKWLSBTB2cjs.cn.call(void 0,
|
|
185
163
|
"absolute inset-0 rounded-full",
|
|
186
|
-
|
|
164
|
+
error || busy || capture.state !== "recording" ? "bg-destructive/20" : "bg-destructive/20 animate-ping"
|
|
187
165
|
)
|
|
188
166
|
}
|
|
189
167
|
),
|
|
@@ -193,7 +171,7 @@ function VoiceDictationButton({
|
|
|
193
171
|
"aria-hidden": true,
|
|
194
172
|
className: _chunkKWLSBTB2cjs.cn.call(void 0,
|
|
195
173
|
"absolute inset-2 rounded-full",
|
|
196
|
-
|
|
174
|
+
"bg-destructive/40"
|
|
197
175
|
)
|
|
198
176
|
}
|
|
199
177
|
),
|
|
@@ -206,8 +184,8 @@ function VoiceDictationButton({
|
|
|
206
184
|
transcript ? " " : "",
|
|
207
185
|
interim
|
|
208
186
|
] })
|
|
209
|
-
] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground italic text-base", children: "Start speaking
|
|
210
|
-
|
|
187
|
+
] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground italic text-base", children: capture.state === "transcribing" ? "Converting your recording to text\u2026" : capture.state === "requesting" ? "Allow microphone access to start." : "Start speaking, then choose Done." }) }),
|
|
188
|
+
error && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { role: "alert", className: "text-sm text-destructive mt-4", children: error })
|
|
211
189
|
] }),
|
|
212
190
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "border-t border-border px-4 py-4 pb-[calc(1rem+env(safe-area-inset-bottom))] md:pb-4 md:rounded-b-xl flex flex-col items-stretch gap-2", children: [
|
|
213
191
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
@@ -215,11 +193,12 @@ function VoiceDictationButton({
|
|
|
215
193
|
{
|
|
216
194
|
size: "lg",
|
|
217
195
|
className: "h-12 w-full",
|
|
218
|
-
onClick: handleDone,
|
|
219
|
-
disabled:
|
|
220
|
-
children: "Done"
|
|
196
|
+
onClick: () => void handleDone(),
|
|
197
|
+
disabled: busy,
|
|
198
|
+
children: capture.state === "transcribing" ? "Transcribing\u2026" : capture.canRetry ? "Retry transcription" : error ? "Try again" : "Done"
|
|
221
199
|
}
|
|
222
200
|
),
|
|
201
|
+
capture.canRetry && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkJDEXZGIYcjs.Button, { variant: "ghost", onClick: () => void capture.start(), children: "Record again" }),
|
|
223
202
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
224
203
|
"button",
|
|
225
204
|
{
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
AgentAudioVisualizerBar
|
|
4
|
+
} from "./chunk-VBKJXNEL.js";
|
|
5
|
+
import {
|
|
6
|
+
Button
|
|
7
|
+
} from "./chunk-W326GKML.js";
|
|
8
|
+
import {
|
|
9
|
+
cn
|
|
10
|
+
} from "./chunk-G6SRSWXB.js";
|
|
11
|
+
|
|
12
|
+
// src/primitives/voice-session-controls.tsx
|
|
13
|
+
import { useSyncExternalStore } from "react";
|
|
14
|
+
import { Mic, MicOff, PhoneOff, AudioLines } from "lucide-react";
|
|
15
|
+
import { Fragment, jsx, jsxs } from "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",
|
|
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
|
+
};
|
|
35
|
+
function VoiceSessionControls({
|
|
36
|
+
state,
|
|
37
|
+
onStart,
|
|
38
|
+
onEnd,
|
|
39
|
+
muted = false,
|
|
40
|
+
onMutedChange,
|
|
41
|
+
disabled = false,
|
|
42
|
+
error,
|
|
43
|
+
volumeBands,
|
|
44
|
+
audioLevels,
|
|
45
|
+
labels,
|
|
46
|
+
size = "icon",
|
|
47
|
+
className
|
|
48
|
+
}) {
|
|
49
|
+
const liveBands = useSyncExternalStore(audioLevels?.subscribe ?? noSubscription, audioLevels?.getSnapshot ?? silence, silence);
|
|
50
|
+
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
51
|
+
const active = state !== "disconnected" && state !== "error";
|
|
52
|
+
const waiting = state === "connecting" || state === "reconnecting";
|
|
53
|
+
return /* @__PURE__ */ jsxs(
|
|
54
|
+
"div",
|
|
55
|
+
{
|
|
56
|
+
"data-slot": "voice-session-controls",
|
|
57
|
+
"data-state": state,
|
|
58
|
+
className: cn("flex min-w-0 items-center gap-2", active && "w-full flex-col gap-3 py-4", !active && "flex-wrap", className),
|
|
59
|
+
children: [
|
|
60
|
+
active ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
61
|
+
/* @__PURE__ */ jsx(
|
|
62
|
+
AgentAudioVisualizerBar,
|
|
63
|
+
{
|
|
64
|
+
"aria-hidden": "true",
|
|
65
|
+
size: "sm",
|
|
66
|
+
barCount: 5,
|
|
67
|
+
state: state === "reconnecting" ? "connecting" : state,
|
|
68
|
+
volumeBands: volumeBands ?? liveBands
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
/* @__PURE__ */ jsx("span", { role: "status", className: "text-sm text-muted-foreground", children: muted && state === "listening" ? copy.muted : copy[state] }),
|
|
72
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
73
|
+
/* @__PURE__ */ jsx(
|
|
74
|
+
Button,
|
|
75
|
+
{
|
|
76
|
+
type: "button",
|
|
77
|
+
variant: "ghost",
|
|
78
|
+
size: "icon",
|
|
79
|
+
className: "min-h-[44px] min-w-[44px] shrink-0",
|
|
80
|
+
"aria-label": muted ? copy.unmute : copy.mute,
|
|
81
|
+
title: muted ? copy.unmute : copy.mute,
|
|
82
|
+
"aria-pressed": muted,
|
|
83
|
+
disabled: waiting,
|
|
84
|
+
onClick: () => onMutedChange(!muted),
|
|
85
|
+
children: muted ? /* @__PURE__ */ jsx(MicOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Mic, { "aria-hidden": "true" })
|
|
86
|
+
}
|
|
87
|
+
),
|
|
88
|
+
/* @__PURE__ */ jsxs(
|
|
89
|
+
Button,
|
|
90
|
+
{
|
|
91
|
+
type: "button",
|
|
92
|
+
variant: "outline",
|
|
93
|
+
shape: "pill",
|
|
94
|
+
className: "min-h-[44px] shrink-0",
|
|
95
|
+
"aria-label": copy.end,
|
|
96
|
+
title: copy.end,
|
|
97
|
+
onClick: onEnd,
|
|
98
|
+
children: [
|
|
99
|
+
/* @__PURE__ */ jsx(PhoneOff, { "aria-hidden": "true" }),
|
|
100
|
+
copy.end
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] })
|
|
105
|
+
] }) : /* @__PURE__ */ jsx(
|
|
106
|
+
Button,
|
|
107
|
+
{
|
|
108
|
+
type: "button",
|
|
109
|
+
size: "icon",
|
|
110
|
+
shape: "circle",
|
|
111
|
+
disabled,
|
|
112
|
+
onClick: onStart,
|
|
113
|
+
"aria-label": state === "error" ? copy.retry : copy.start,
|
|
114
|
+
title: state === "error" ? copy.retry : copy.start,
|
|
115
|
+
className: cn("min-h-[44px] min-w-[44px] shrink-0 rounded-full", size === "hero" && "size-24 [&_svg]:size-7"),
|
|
116
|
+
children: /* @__PURE__ */ jsx(AudioLines, { "aria-hidden": "true" })
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
state === "error" && /* @__PURE__ */ jsx("span", { role: "alert", className: "basis-full text-xs text-destructive", children: error || copy.error })
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
VoiceSessionControls
|
|
127
|
+
};
|