@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.
- package/README.md +72 -1
- package/THIRD_PARTY_NOTICES.md +14 -0
- package/dist/chunk-2BSFYU22.cjs +71 -0
- package/dist/chunk-5OF6D3GH.js +158 -0
- package/dist/chunk-7QTFYEPE.cjs +153 -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-HLAHOVEI.js +412 -0
- package/dist/{chunk-F3B4M6DU.js → chunk-HTPPNW74.js} +1 -1
- package/dist/chunk-KQBXAQAY.cjs +221 -0
- package/dist/{chunk-JN6JOSZG.js → chunk-LQPJGZIE.js} +7 -7
- package/dist/chunk-NKWHDI4C.cjs +158 -0
- package/dist/chunk-T3XMHWYV.js +221 -0
- package/dist/chunk-UOVA4YKW.js +153 -0
- package/dist/chunk-ZZ7ICQR2.cjs +412 -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 +61 -0
- package/dist/hooks/use-dictation.d.ts +61 -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/alert.d.cts +1 -1
- package/dist/primitives/alert.d.ts +1 -1
- package/dist/primitives/archive-confirmation-dialog.cjs +2 -2
- package/dist/primitives/archive-confirmation-dialog.js +3 -3
- package/dist/primitives/attachment.d.cts +1 -1
- package/dist/primitives/attachment.d.ts +1 -1
- package/dist/primitives/badge.d.cts +1 -1
- package/dist/primitives/badge.d.ts +1 -1
- package/dist/primitives/board-view.cjs +6 -6
- package/dist/primitives/board-view.js +5 -5
- package/dist/primitives/bubble.d.cts +1 -1
- package/dist/primitives/bubble.d.ts +1 -1
- package/dist/primitives/button.d.cts +2 -2
- package/dist/primitives/button.d.ts +2 -2
- package/dist/primitives/chat.cjs +59 -28
- package/dist/primitives/chat.d.cts +8 -3
- package/dist/primitives/chat.d.ts +8 -3
- package/dist/primitives/chat.js +56 -25
- 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/item.d.cts +2 -2
- package/dist/primitives/item.d.ts +2 -2
- package/dist/primitives/resource-list.cjs +6 -6
- package/dist/primitives/resource-list.js +5 -5
- package/dist/primitives/section-header.d.cts +1 -1
- package/dist/primitives/section-header.d.ts +1 -1
- 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/toggle-group.d.cts +2 -2
- package/dist/primitives/toggle-group.d.ts +2 -2
- package/dist/primitives/toggle.d.cts +2 -2
- package/dist/primitives/toggle.d.ts +2 -2
- package/dist/primitives/voice-dictation-button.cjs +5 -2
- package/dist/primitives/voice-dictation-button.d.cts +5 -9
- package/dist/primitives/voice-dictation-button.d.ts +5 -9
- package/dist/primitives/voice-dictation-button.js +4 -1
- package/dist/primitives/voice-session-controls.cjs +12 -0
- package/dist/primitives/voice-session-controls.d.cts +31 -0
- package/dist/primitives/voice-session-controls.d.ts +31 -0
- package/dist/primitives/voice-session-controls.js +12 -0
- package/package.json +51 -1
- package/dist/chunk-LKPM6DQP.cjs +0 -244
- package/dist/chunk-TI57GAQP.js +0 -244
- 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
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
var _chunk2BSFYU22cjs = require('./chunk-2BSFYU22.cjs');
|
|
8
|
+
|
|
9
|
+
// src/primitives/agent-audio-visualizer-bar.tsx
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
var _react = require('react'); var _react2 = _interopRequireDefault(_react);
|
|
16
|
+
var _classvarianceauthority = require('class-variance-authority');
|
|
17
|
+
var _jsxruntime = require('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 = _nullishCoalesce(bands[bands.length - 1], () => ( 0));
|
|
22
|
+
return [...bands, ...new Array(count - bands.length).fill(lastValue)];
|
|
23
|
+
}
|
|
24
|
+
function cloneSingleChild(children, props, key) {
|
|
25
|
+
return _react.Children.map(children, (child) => {
|
|
26
|
+
if (_react.isValidElement.call(void 0, child) && _react.Children.only(children)) {
|
|
27
|
+
const childProps = child.props;
|
|
28
|
+
if (childProps.className) {
|
|
29
|
+
props ??= {};
|
|
30
|
+
props.className = _chunkKWLSBTB2cjs.cn.call(void 0, childProps.className, props.className);
|
|
31
|
+
props.style = {
|
|
32
|
+
...childProps.style,
|
|
33
|
+
...props.style
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return _react.cloneElement.call(void 0, child, { ...props, key: key ? String(key) : void 0 });
|
|
37
|
+
}
|
|
38
|
+
return child;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
var AgentAudioVisualizerBarElementVariants = _classvarianceauthority.cva.call(void 0,
|
|
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 = _classvarianceauthority.cva.call(void 0, "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 = _react.useMemo.call(void 0, () => {
|
|
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(_nullishCoalesce(volumeBands, () => ( [])), _barCount).map((value) => Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0);
|
|
99
|
+
const sequencerInterval = _react.useMemo.call(void 0, () => {
|
|
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 = _chunk2BSFYU22cjs.useAgentAudioVisualizerBarAnimator.call(void 0,
|
|
115
|
+
meteredListening ? "speaking" : state,
|
|
116
|
+
_barCount,
|
|
117
|
+
sequencerInterval
|
|
118
|
+
);
|
|
119
|
+
const bands = _react.useMemo.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
127
|
+
"div",
|
|
128
|
+
{
|
|
129
|
+
"data-lk-state": state,
|
|
130
|
+
style: { ...style, color },
|
|
131
|
+
className: _chunkKWLSBTB2cjs.cn.call(void 0, AgentAudioVisualizerBarVariants({ size }), className),
|
|
132
|
+
...props,
|
|
133
|
+
children: bands.map(
|
|
134
|
+
(band, idx) => children ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react2.default.Fragment, { children: cloneSingleChild(children, {
|
|
135
|
+
"data-lk-index": idx,
|
|
136
|
+
"data-lk-highlighted": highlightedIndices.includes(idx),
|
|
137
|
+
style: { height: `${band * 100}%` }
|
|
138
|
+
}) }, idx) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
139
|
+
"div",
|
|
140
|
+
{
|
|
141
|
+
"data-lk-index": idx,
|
|
142
|
+
"data-lk-highlighted": highlightedIndices.includes(idx),
|
|
143
|
+
style: { height: `${band * 100}%` },
|
|
144
|
+
className: _chunkKWLSBTB2cjs.cn.call(void 0, AgentAudioVisualizerBarElementVariants({ size }))
|
|
145
|
+
},
|
|
146
|
+
idx
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
exports.normalizeVolumeBands = normalizeVolumeBands; exports.AgentAudioVisualizerBarElementVariants = AgentAudioVisualizerBarElementVariants; exports.AgentAudioVisualizerBarVariants = AgentAudioVisualizerBarVariants; exports.AgentAudioVisualizerBar = AgentAudioVisualizerBar;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
AgentAudioVisualizerBar
|
|
4
|
+
} from "./chunk-5OF6D3GH.js";
|
|
5
|
+
import {
|
|
6
|
+
Button
|
|
7
|
+
} from "./chunk-W326GKML.js";
|
|
8
|
+
import {
|
|
9
|
+
cn
|
|
10
|
+
} from "./chunk-G6SRSWXB.js";
|
|
11
|
+
import {
|
|
12
|
+
useDictation
|
|
13
|
+
} from "./chunk-HLAHOVEI.js";
|
|
14
|
+
|
|
15
|
+
// src/primitives/voice-dictation-button.tsx
|
|
16
|
+
import {
|
|
17
|
+
useCallback,
|
|
18
|
+
useEffect,
|
|
19
|
+
useRef,
|
|
20
|
+
useState,
|
|
21
|
+
useSyncExternalStore
|
|
22
|
+
} from "react";
|
|
23
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
24
|
+
import { Mic, LoaderCircle } from "lucide-react";
|
|
25
|
+
import { Fragment, jsx, jsxs } from "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] = useState(false);
|
|
42
|
+
const capture = useDictation({ lang, continuous });
|
|
43
|
+
const { transcript, interimTranscript: interim, supported, error } = capture;
|
|
44
|
+
const generation = useRef(0);
|
|
45
|
+
const finishing = useRef(false);
|
|
46
|
+
const previousFocus = useRef(null);
|
|
47
|
+
const elapsed = useRecordingTime(capture.state);
|
|
48
|
+
const busy = capture.state === "requesting" || capture.state === "transcribing";
|
|
49
|
+
const openOverlay = useCallback(() => {
|
|
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 = useCallback(() => {
|
|
57
|
+
generation.current++;
|
|
58
|
+
finishing.current = false;
|
|
59
|
+
capture.cancel();
|
|
60
|
+
setOpen(false);
|
|
61
|
+
}, [capture.cancel]);
|
|
62
|
+
const handleDone = useCallback(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 onCancel?.();
|
|
77
|
+
}, [busy, capture.canRetry, capture.retry, capture.start, capture.state, capture.stop, onCommit, onCancel, closeOverlay]);
|
|
78
|
+
const handleCancel = useCallback(() => {
|
|
79
|
+
closeOverlay();
|
|
80
|
+
onCancel?.();
|
|
81
|
+
}, [closeOverlay, onCancel]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (open && capture.state === "idle") handleCancel();
|
|
84
|
+
}, [capture.state, handleCancel, open]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
return () => {
|
|
87
|
+
generation.current++;
|
|
88
|
+
};
|
|
89
|
+
}, []);
|
|
90
|
+
if (!supported) return null;
|
|
91
|
+
const displayTranscript = `${transcript}${interim ? (transcript ? " " : "") + interim : ""}`;
|
|
92
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
93
|
+
children ? /* @__PURE__ */ jsx(
|
|
94
|
+
"button",
|
|
95
|
+
{
|
|
96
|
+
type: "button",
|
|
97
|
+
onClick: openOverlay,
|
|
98
|
+
disabled,
|
|
99
|
+
"aria-label": "Start dictation",
|
|
100
|
+
className: cn(visibility === "hidden" && "hidden", className),
|
|
101
|
+
children
|
|
102
|
+
}
|
|
103
|
+
) : /* @__PURE__ */ jsx(
|
|
104
|
+
Button,
|
|
105
|
+
{
|
|
106
|
+
type: "button",
|
|
107
|
+
onClick: openOverlay,
|
|
108
|
+
disabled,
|
|
109
|
+
variant: "outline",
|
|
110
|
+
size,
|
|
111
|
+
"aria-label": "Start dictation",
|
|
112
|
+
className: cn("transition-colors", visibility === "hidden" && "hidden", !shrink && "shrink-0", className),
|
|
113
|
+
children: /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-center gap-2", children: [
|
|
114
|
+
/* @__PURE__ */ jsx(Mic, { className: "h-4 w-4" }),
|
|
115
|
+
showLabel && /* @__PURE__ */ jsx("span", { children: "Speak" })
|
|
116
|
+
] })
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
/* @__PURE__ */ jsx(
|
|
120
|
+
DialogPrimitive.Root,
|
|
121
|
+
{
|
|
122
|
+
open,
|
|
123
|
+
onOpenChange: (next) => {
|
|
124
|
+
if (!next) handleCancel();
|
|
125
|
+
},
|
|
126
|
+
children: /* @__PURE__ */ jsx(DialogPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
|
|
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 (previousFocus.current?.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__ */ jsxs("div", { className: "flex min-w-0 items-start justify-between gap-3", children: [
|
|
146
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
|
|
147
|
+
/* @__PURE__ */ jsx(DialogPrimitive.Title, { className: "text-sm font-medium", children: title || "Voice dictation" }),
|
|
148
|
+
/* @__PURE__ */ jsxs("span", { role: "status", className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
149
|
+
busy ? /* @__PURE__ */ jsx(LoaderCircle, { "aria-hidden": "true", className: "size-3.5 animate-spin motion-reduce:animate-none" }) : /* @__PURE__ */ jsx(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__ */ jsxs("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__ */ jsx(DictationWaveform, { source: capture.audioLevels, state: capture.state }),
|
|
160
|
+
/* @__PURE__ */ jsx(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__ */ jsxs("p", { className: "max-h-24 overflow-y-auto text-sm leading-relaxed", children: [
|
|
162
|
+
transcript,
|
|
163
|
+
interim && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
164
|
+
transcript ? " " : "",
|
|
165
|
+
interim
|
|
166
|
+
] })
|
|
167
|
+
] }),
|
|
168
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-xs text-destructive", children: error }),
|
|
169
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [
|
|
170
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "min-h-[44px]", onClick: handleCancel, children: "Cancel" }),
|
|
171
|
+
capture.canRetry && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", className: "min-h-[44px]", onClick: () => void capture.start(), children: "Record again" }),
|
|
172
|
+
/* @__PURE__ */ jsx(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 = useSyncExternalStore(source.subscribe, source.getSnapshot, silence);
|
|
185
|
+
return /* @__PURE__ */ jsx(
|
|
186
|
+
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] = useState(0);
|
|
200
|
+
const startedAt = useRef(null);
|
|
201
|
+
useEffect(() => {
|
|
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
|
+
export {
|
|
220
|
+
VoiceDictationButton
|
|
221
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
AgentAudioVisualizerBar
|
|
4
|
+
} from "./chunk-5OF6D3GH.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, Square, AudioLines } from "lucide-react";
|
|
15
|
+
import { 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\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 = useSyncExternalStore(audioLevels?.subscribe ?? noSubscription, audioLevels?.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__ */ jsxs(
|
|
59
|
+
"div",
|
|
60
|
+
{
|
|
61
|
+
"data-slot": "voice-session-controls",
|
|
62
|
+
"data-state": state,
|
|
63
|
+
className: cn("min-w-0", active ? "@container/voice w-full" : "flex flex-wrap items-center gap-2", className),
|
|
64
|
+
children: [
|
|
65
|
+
active ? /* @__PURE__ */ jsxs("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__ */ jsxs("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__ */ jsx("div", { "aria-hidden": "true", className: cn(
|
|
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__ */ jsx(MicOff, { className: "size-5" }) : /* @__PURE__ */ jsx(Mic, { className: "size-5" }) }),
|
|
71
|
+
/* @__PURE__ */ jsx(
|
|
72
|
+
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 : volumeBands ?? liveBands
|
|
80
|
+
}
|
|
81
|
+
),
|
|
82
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
|
|
83
|
+
/* @__PURE__ */ jsx("span", { role: "status", className: "block text-sm font-medium text-primary", children: muted && state === "listening" ? copy.muted : copy[state] }),
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
"p",
|
|
86
|
+
{
|
|
87
|
+
"data-slot": "voice-session-transcript",
|
|
88
|
+
title: transcript || void 0,
|
|
89
|
+
className: cn("truncate text-sm font-normal", transcript ? "text-foreground" : "text-muted-foreground"),
|
|
90
|
+
children: transcript || copy.transcriptPlaceholder
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
] })
|
|
94
|
+
] }),
|
|
95
|
+
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
|
|
96
|
+
/* @__PURE__ */ jsxs(
|
|
97
|
+
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__ */ jsx(Mic, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(MicOff, { "aria-hidden": "true" }),
|
|
110
|
+
muted ? copy.unmuteShort : copy.muteShort
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
/* @__PURE__ */ jsxs(
|
|
115
|
+
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__ */ jsx(Square, { "aria-hidden": "true", className: "fill-current" }),
|
|
126
|
+
copy.endShort
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
] })
|
|
131
|
+
] }) : /* @__PURE__ */ jsx(
|
|
132
|
+
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: cn("min-h-[44px] min-w-[44px] shrink-0 rounded-full", size === "hero" && "size-24 [&_svg]:size-7"),
|
|
142
|
+
children: /* @__PURE__ */ jsx(AudioLines, { "aria-hidden": "true" })
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
state === "error" && /* @__PURE__ */ jsx("span", { role: "alert", className: "basis-full text-xs text-destructive", children: error || copy.error })
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export {
|
|
152
|
+
VoiceSessionControls
|
|
153
|
+
};
|