@fluencypassdevs/cycle 1.6.0 → 1.9.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/bin/mcp.mjs +150 -0
- package/dist/chunk-37C2K2NM.js +101 -0
- package/dist/chunk-37C2K2NM.js.map +1 -0
- package/dist/chunk-I5A3CME4.js +84 -0
- package/dist/chunk-I5A3CME4.js.map +1 -0
- package/dist/chunk-JRJBKPXW.js +118 -0
- package/dist/chunk-JRJBKPXW.js.map +1 -0
- package/dist/{chunk-PCLVQPIG.js → chunk-L2DJS6PG.js} +3 -3
- package/dist/{chunk-PCLVQPIG.js.map → chunk-L2DJS6PG.js.map} +1 -1
- package/dist/chunk-PW464XEP.js +787 -0
- package/dist/chunk-PW464XEP.js.map +1 -0
- package/dist/chunk-QANROH2K.js +50 -0
- package/dist/chunk-QANROH2K.js.map +1 -0
- package/dist/chunk-YMWRR7ET.js +503 -0
- package/dist/chunk-YMWRR7ET.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +18 -12
- package/dist/layout/container.d.ts +24 -0
- package/dist/layout/container.js +5 -0
- package/dist/layout/container.js.map +1 -0
- package/dist/layout/grid.d.ts +26 -0
- package/dist/layout/grid.js +5 -0
- package/dist/layout/grid.js.map +1 -0
- package/dist/layout/stack.d.ts +47 -0
- package/dist/layout/stack.js +5 -0
- package/dist/layout/stack.js.map +1 -0
- package/dist/types-CVdl5nka.d.ts +9 -0
- package/dist/ui/chat-message.d.ts +57 -0
- package/dist/ui/chat-message.js +11 -0
- package/dist/ui/chat-message.js.map +1 -0
- package/dist/ui/chat-panel.js +1 -1
- package/dist/ui/live-waiting.js +2 -2
- package/dist/ui/message-bar.d.ts +69 -0
- package/dist/ui/message-bar.js +5 -0
- package/dist/ui/message-bar.js.map +1 -0
- package/dist/ui/message-rating.d.ts +29 -0
- package/dist/ui/message-rating.js +5 -0
- package/dist/ui/message-rating.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
import { cn } from './chunk-TYCPXAXF.js';
|
|
2
|
+
import { __objRest, __spreadProps, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Trash2, Pause, Play, Mic, ChevronLeft, Lock, ChevronUp, Send } from 'lucide-react';
|
|
5
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function formatDuration(seconds) {
|
|
8
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "00:00";
|
|
9
|
+
const m = Math.floor(seconds / 60);
|
|
10
|
+
const s = Math.floor(seconds % 60);
|
|
11
|
+
return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
|
|
12
|
+
}
|
|
13
|
+
function MicButton({
|
|
14
|
+
onClick,
|
|
15
|
+
onPointerDown,
|
|
16
|
+
onPointerMove,
|
|
17
|
+
onPointerUp,
|
|
18
|
+
onPointerCancel,
|
|
19
|
+
size = "default",
|
|
20
|
+
variant = "default",
|
|
21
|
+
pressed = false,
|
|
22
|
+
ariaLabel = "Gravar audio",
|
|
23
|
+
className,
|
|
24
|
+
style
|
|
25
|
+
}) {
|
|
26
|
+
const sizeClass = variant === "large" ? "size-16" : size === "default" ? "size-10" : "size-8";
|
|
27
|
+
const radiusClass = variant === "large" ? "rounded-lg" : "rounded-md";
|
|
28
|
+
return /* @__PURE__ */ jsx(
|
|
29
|
+
"button",
|
|
30
|
+
{
|
|
31
|
+
type: "button",
|
|
32
|
+
onClick,
|
|
33
|
+
onPointerDown,
|
|
34
|
+
onPointerMove,
|
|
35
|
+
onPointerUp,
|
|
36
|
+
onPointerCancel,
|
|
37
|
+
"aria-pressed": pressed || void 0,
|
|
38
|
+
className: cn(
|
|
39
|
+
"shrink-0 inline-flex items-center justify-center bg-primary text-primary-foreground theme-brand transition-all hover:opacity-90 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
40
|
+
sizeClass,
|
|
41
|
+
radiusClass,
|
|
42
|
+
pressed && "scale-110 opacity-90 touch-none select-none",
|
|
43
|
+
className
|
|
44
|
+
),
|
|
45
|
+
style,
|
|
46
|
+
"aria-label": ariaLabel,
|
|
47
|
+
children: /* @__PURE__ */ jsx(Mic, { className: variant === "large" ? "size-6" : "size-5" })
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function SendButton({
|
|
52
|
+
onClick,
|
|
53
|
+
disabled,
|
|
54
|
+
ariaLabel = "Enviar"
|
|
55
|
+
}) {
|
|
56
|
+
return /* @__PURE__ */ jsx(
|
|
57
|
+
"button",
|
|
58
|
+
{
|
|
59
|
+
type: "button",
|
|
60
|
+
onClick,
|
|
61
|
+
disabled,
|
|
62
|
+
className: "shrink-0 inline-flex items-center justify-center size-10 rounded-md bg-primary text-primary-foreground theme-positive transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50 disabled:cursor-not-allowed",
|
|
63
|
+
"aria-label": ariaLabel,
|
|
64
|
+
children: /* @__PURE__ */ jsx(Send, { className: "size-5", fill: "currentColor", strokeWidth: 0 })
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
function IconButton({
|
|
69
|
+
onClick,
|
|
70
|
+
icon: Icon,
|
|
71
|
+
ariaLabel,
|
|
72
|
+
filled = false
|
|
73
|
+
}) {
|
|
74
|
+
return /* @__PURE__ */ jsx(
|
|
75
|
+
"button",
|
|
76
|
+
{
|
|
77
|
+
type: "button",
|
|
78
|
+
onClick,
|
|
79
|
+
className: "shrink-0 inline-flex items-center justify-center size-10 rounded-md text-neutral-foreground hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
80
|
+
"aria-label": ariaLabel,
|
|
81
|
+
children: /* @__PURE__ */ jsx(
|
|
82
|
+
Icon,
|
|
83
|
+
__spreadValues({
|
|
84
|
+
className: "size-5"
|
|
85
|
+
}, filled ? { fill: "currentColor", strokeWidth: 0 } : {})
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
function RecordingIndicator() {
|
|
91
|
+
return /* @__PURE__ */ jsxs("span", { className: "shrink-0 relative inline-flex items-center justify-center size-3", "aria-label": "Gravando", children: [
|
|
92
|
+
/* @__PURE__ */ jsx("span", { className: "absolute inset-0 rounded-full bg-primary theme-brand animate-ping opacity-75" }),
|
|
93
|
+
/* @__PURE__ */ jsx("span", { className: "relative size-2.5 rounded-full bg-primary theme-brand" })
|
|
94
|
+
] });
|
|
95
|
+
}
|
|
96
|
+
function aggregateSamples(samples, barCount) {
|
|
97
|
+
var _a;
|
|
98
|
+
if (samples.length === 0 || barCount === 0) return new Array(barCount).fill(0);
|
|
99
|
+
const result = new Array(barCount).fill(0);
|
|
100
|
+
if (samples.length < barCount) {
|
|
101
|
+
for (let i = 0; i < barCount; i++) {
|
|
102
|
+
const idx = Math.floor(i / barCount * samples.length);
|
|
103
|
+
result[i] = (_a = samples[idx]) != null ? _a : 0;
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
const chunkSize = samples.length / barCount;
|
|
108
|
+
for (let i = 0; i < barCount; i++) {
|
|
109
|
+
const start = Math.floor(i * chunkSize);
|
|
110
|
+
const end = Math.floor((i + 1) * chunkSize);
|
|
111
|
+
let max = 0;
|
|
112
|
+
for (let j = start; j < end && j < samples.length; j++) {
|
|
113
|
+
if (samples[j] > max) max = samples[j];
|
|
114
|
+
}
|
|
115
|
+
result[i] = max;
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function LiveWaveform({
|
|
120
|
+
stream,
|
|
121
|
+
samplesRef
|
|
122
|
+
}) {
|
|
123
|
+
const STRIDE = 4;
|
|
124
|
+
const MIN_BARS = 20;
|
|
125
|
+
const containerRef = React.useRef(null);
|
|
126
|
+
const [bars, setBars] = React.useState([]);
|
|
127
|
+
const barCountRef = React.useRef(MIN_BARS);
|
|
128
|
+
React.useEffect(() => {
|
|
129
|
+
const el = containerRef.current;
|
|
130
|
+
if (!el) return;
|
|
131
|
+
const updateBarCount = (width) => {
|
|
132
|
+
const count = Math.max(MIN_BARS, Math.floor(width / STRIDE));
|
|
133
|
+
barCountRef.current = count;
|
|
134
|
+
setBars((prev) => prev.length > count ? prev.slice(prev.length - count) : prev);
|
|
135
|
+
};
|
|
136
|
+
updateBarCount(el.clientWidth);
|
|
137
|
+
const ro = new ResizeObserver((entries) => {
|
|
138
|
+
var _a, _b;
|
|
139
|
+
const width = (_b = (_a = entries[0]) == null ? void 0 : _a.contentRect.width) != null ? _b : 0;
|
|
140
|
+
updateBarCount(width);
|
|
141
|
+
});
|
|
142
|
+
ro.observe(el);
|
|
143
|
+
return () => ro.disconnect();
|
|
144
|
+
}, []);
|
|
145
|
+
React.useEffect(() => {
|
|
146
|
+
if (!stream) {
|
|
147
|
+
setBars([]);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
151
|
+
const audioContext = new AudioContextClass();
|
|
152
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
153
|
+
const analyser = audioContext.createAnalyser();
|
|
154
|
+
analyser.fftSize = 256;
|
|
155
|
+
source.connect(analyser);
|
|
156
|
+
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
|
157
|
+
let rafId = null;
|
|
158
|
+
let lastUpdate = 0;
|
|
159
|
+
let cancelled = false;
|
|
160
|
+
const tick = (timestamp) => {
|
|
161
|
+
if (cancelled) return;
|
|
162
|
+
if (timestamp - lastUpdate >= 33) {
|
|
163
|
+
lastUpdate = timestamp;
|
|
164
|
+
analyser.getByteTimeDomainData(dataArray);
|
|
165
|
+
let sum = 0;
|
|
166
|
+
for (let i = 0; i < dataArray.length; i++) {
|
|
167
|
+
const v = (dataArray[i] - 128) / 128;
|
|
168
|
+
sum += v * v;
|
|
169
|
+
}
|
|
170
|
+
const rms = Math.sqrt(sum / dataArray.length);
|
|
171
|
+
if (samplesRef) samplesRef.current.push(rms);
|
|
172
|
+
setBars((prev) => {
|
|
173
|
+
if (prev.length < barCountRef.current) return [...prev, rms];
|
|
174
|
+
return [...prev.slice(1), rms];
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
rafId = requestAnimationFrame(tick);
|
|
178
|
+
};
|
|
179
|
+
rafId = requestAnimationFrame(tick);
|
|
180
|
+
return () => {
|
|
181
|
+
cancelled = true;
|
|
182
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
183
|
+
void audioContext.close();
|
|
184
|
+
};
|
|
185
|
+
}, [stream, samplesRef]);
|
|
186
|
+
return /* @__PURE__ */ jsx(
|
|
187
|
+
"div",
|
|
188
|
+
{
|
|
189
|
+
ref: containerRef,
|
|
190
|
+
className: "flex-1 relative h-8 min-w-0 overflow-hidden",
|
|
191
|
+
children: /* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 left-0 right-0 flex items-center justify-end gap-[2px]", children: bars.map((amp, i) => {
|
|
192
|
+
const normalized = Math.min(amp * 4, 1);
|
|
193
|
+
const height = Math.max(2, Math.round(normalized * 32));
|
|
194
|
+
return /* @__PURE__ */ jsx(
|
|
195
|
+
"div",
|
|
196
|
+
{
|
|
197
|
+
className: "w-[2px] shrink-0 rounded-full bg-neutral-foreground",
|
|
198
|
+
style: { height: `${height}px` }
|
|
199
|
+
},
|
|
200
|
+
i
|
|
201
|
+
);
|
|
202
|
+
}) })
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
function RecordedWaveform({
|
|
207
|
+
samples,
|
|
208
|
+
progress = 0,
|
|
209
|
+
onSeek
|
|
210
|
+
}) {
|
|
211
|
+
const STRIDE = 4;
|
|
212
|
+
const MIN_BARS = 20;
|
|
213
|
+
const containerRef = React.useRef(null);
|
|
214
|
+
const [barCount, setBarCount] = React.useState(MIN_BARS);
|
|
215
|
+
React.useEffect(() => {
|
|
216
|
+
const el = containerRef.current;
|
|
217
|
+
if (!el) return;
|
|
218
|
+
const updateBarCount = (width) => {
|
|
219
|
+
const count = Math.max(MIN_BARS, Math.floor(width / STRIDE));
|
|
220
|
+
setBarCount(count);
|
|
221
|
+
};
|
|
222
|
+
updateBarCount(el.clientWidth);
|
|
223
|
+
const ro = new ResizeObserver((entries) => {
|
|
224
|
+
var _a, _b;
|
|
225
|
+
updateBarCount((_b = (_a = entries[0]) == null ? void 0 : _a.contentRect.width) != null ? _b : 0);
|
|
226
|
+
});
|
|
227
|
+
ro.observe(el);
|
|
228
|
+
return () => ro.disconnect();
|
|
229
|
+
}, []);
|
|
230
|
+
const bars = React.useMemo(() => {
|
|
231
|
+
const aggregated = aggregateSamples(samples, barCount);
|
|
232
|
+
const maxPeak = Math.max(...aggregated, 0.01);
|
|
233
|
+
return aggregated.map((p) => p / maxPeak);
|
|
234
|
+
}, [samples, barCount]);
|
|
235
|
+
const isDraggingRef = React.useRef(false);
|
|
236
|
+
const seekFromClientX = React.useCallback(
|
|
237
|
+
(clientX) => {
|
|
238
|
+
const el = containerRef.current;
|
|
239
|
+
if (!el || !onSeek) return;
|
|
240
|
+
const rect = el.getBoundingClientRect();
|
|
241
|
+
const x = clientX - rect.left;
|
|
242
|
+
const newProgress = Math.max(0, Math.min(x / rect.width, 1));
|
|
243
|
+
onSeek(newProgress);
|
|
244
|
+
},
|
|
245
|
+
[onSeek]
|
|
246
|
+
);
|
|
247
|
+
const handlePointerDown = (e) => {
|
|
248
|
+
if (!onSeek) return;
|
|
249
|
+
isDraggingRef.current = true;
|
|
250
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
251
|
+
seekFromClientX(e.clientX);
|
|
252
|
+
};
|
|
253
|
+
const handlePointerMove = (e) => {
|
|
254
|
+
if (!isDraggingRef.current) return;
|
|
255
|
+
seekFromClientX(e.clientX);
|
|
256
|
+
};
|
|
257
|
+
const handlePointerUp = (e) => {
|
|
258
|
+
if (!isDraggingRef.current) return;
|
|
259
|
+
isDraggingRef.current = false;
|
|
260
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
261
|
+
};
|
|
262
|
+
const barsLayoutWidth = bars.length * STRIDE - 2;
|
|
263
|
+
return /* @__PURE__ */ jsxs(
|
|
264
|
+
"div",
|
|
265
|
+
{
|
|
266
|
+
ref: containerRef,
|
|
267
|
+
className: cn(
|
|
268
|
+
"flex-1 relative h-8 min-w-0 overflow-hidden select-none",
|
|
269
|
+
onSeek && "touch-none cursor-pointer"
|
|
270
|
+
),
|
|
271
|
+
onPointerDown: handlePointerDown,
|
|
272
|
+
onPointerMove: handlePointerMove,
|
|
273
|
+
onPointerUp: handlePointerUp,
|
|
274
|
+
onPointerCancel: handlePointerUp,
|
|
275
|
+
role: onSeek ? "slider" : void 0,
|
|
276
|
+
"aria-label": onSeek ? "Posicao do audio" : void 0,
|
|
277
|
+
"aria-valuemin": onSeek ? 0 : void 0,
|
|
278
|
+
"aria-valuemax": onSeek ? 100 : void 0,
|
|
279
|
+
"aria-valuenow": onSeek ? Math.round(progress * 100) : void 0,
|
|
280
|
+
children: [
|
|
281
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 left-0 flex items-center gap-[2px]", children: bars.map((amp, i) => {
|
|
282
|
+
const isPlayed = i / bars.length <= progress;
|
|
283
|
+
const height = Math.max(2, Math.round(amp * 32));
|
|
284
|
+
return /* @__PURE__ */ jsx(
|
|
285
|
+
"div",
|
|
286
|
+
{
|
|
287
|
+
className: cn(
|
|
288
|
+
"w-[2px] shrink-0 rounded-full",
|
|
289
|
+
isPlayed ? "bg-neutral-foreground" : "bg-neutral-ring"
|
|
290
|
+
),
|
|
291
|
+
style: { height: `${height}px` }
|
|
292
|
+
},
|
|
293
|
+
i
|
|
294
|
+
);
|
|
295
|
+
}) }),
|
|
296
|
+
progress > 0 && /* @__PURE__ */ jsx(
|
|
297
|
+
"div",
|
|
298
|
+
{
|
|
299
|
+
className: "absolute top-1/2 z-10 -translate-y-1/2 -translate-x-1/2 size-3 rounded-full bg-[#098A5E] shadow-md pointer-events-none",
|
|
300
|
+
style: { left: `${progress * barsLayoutWidth}px` },
|
|
301
|
+
"aria-hidden": "true"
|
|
302
|
+
}
|
|
303
|
+
)
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
var MIN_DURATION = 1e3;
|
|
309
|
+
function PressedRecordingOverlay({
|
|
310
|
+
variant,
|
|
311
|
+
duration,
|
|
312
|
+
deltaX,
|
|
313
|
+
deltaY
|
|
314
|
+
}) {
|
|
315
|
+
const aboutToLock = deltaY <= -60;
|
|
316
|
+
const aboutToCancel = deltaX <= -80;
|
|
317
|
+
const micTranslateX = Math.min(0, deltaX);
|
|
318
|
+
const micTranslateY = Math.min(0, deltaY);
|
|
319
|
+
return /* @__PURE__ */ jsx(
|
|
320
|
+
"div",
|
|
321
|
+
{
|
|
322
|
+
"data-slot": "message-bar",
|
|
323
|
+
"data-state": "recording-pressed",
|
|
324
|
+
className: cn(
|
|
325
|
+
"flex items-center gap-2 p-2 sm:gap-3 sm:p-0",
|
|
326
|
+
variant === "audio-only" && "justify-center"
|
|
327
|
+
),
|
|
328
|
+
children: variant === "audio-only" ? /* @__PURE__ */ jsxs("div", { className: "relative flex items-center gap-2", children: [
|
|
329
|
+
/* @__PURE__ */ jsxs(
|
|
330
|
+
"div",
|
|
331
|
+
{
|
|
332
|
+
className: cn(
|
|
333
|
+
"flex items-center gap-1 rounded-full bg-muted h-7 px-3 transition-colors",
|
|
334
|
+
aboutToCancel && "bg-destructive/10"
|
|
335
|
+
),
|
|
336
|
+
children: [
|
|
337
|
+
/* @__PURE__ */ jsx(ChevronLeft, { className: "size-3 text-muted-foreground" }),
|
|
338
|
+
/* @__PURE__ */ jsx(
|
|
339
|
+
"span",
|
|
340
|
+
{
|
|
341
|
+
className: cn(
|
|
342
|
+
"text-xs",
|
|
343
|
+
aboutToCancel ? "text-destructive font-medium" : "text-muted-foreground"
|
|
344
|
+
),
|
|
345
|
+
children: "Cancelar"
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
),
|
|
351
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
352
|
+
/* @__PURE__ */ jsxs(
|
|
353
|
+
"div",
|
|
354
|
+
{
|
|
355
|
+
className: cn(
|
|
356
|
+
"absolute left-1/2 -translate-x-1/2 bottom-full mb-2 flex flex-col items-center gap-3 rounded-t-full bg-muted h-20 w-8 py-3 transition-transform",
|
|
357
|
+
aboutToLock && "scale-125 ring-2 ring-[#0EA460]"
|
|
358
|
+
),
|
|
359
|
+
"aria-hidden": "true",
|
|
360
|
+
children: [
|
|
361
|
+
/* @__PURE__ */ jsx(Lock, { className: cn("size-3", aboutToLock ? "text-[#0EA460]" : "text-muted-foreground") }),
|
|
362
|
+
/* @__PURE__ */ jsx(ChevronUp, { className: "size-3 text-muted-foreground animate-bounce" })
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
),
|
|
366
|
+
/* @__PURE__ */ jsx(
|
|
367
|
+
MicButton,
|
|
368
|
+
{
|
|
369
|
+
variant: "large",
|
|
370
|
+
pressed: true,
|
|
371
|
+
ariaLabel: "Gravando \u2014 arraste para cima para travar, para esquerda para cancelar",
|
|
372
|
+
className: cn(aboutToCancel && "animate-shake"),
|
|
373
|
+
style: aboutToCancel ? void 0 : { transform: `translate(${micTranslateX}px, ${micTranslateY}px)` }
|
|
374
|
+
}
|
|
375
|
+
)
|
|
376
|
+
] }),
|
|
377
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-full bg-muted h-7 px-3", children: [
|
|
378
|
+
/* @__PURE__ */ jsx(RecordingIndicator, {}),
|
|
379
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm tabular-nums text-neutral-foreground", children: formatDuration(duration) })
|
|
380
|
+
] })
|
|
381
|
+
] }) : (
|
|
382
|
+
// Variant default: cancel pill flex-1 + mic
|
|
383
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
384
|
+
/* @__PURE__ */ jsxs(
|
|
385
|
+
"div",
|
|
386
|
+
{
|
|
387
|
+
className: cn(
|
|
388
|
+
"flex-1 flex items-center justify-between gap-2 rounded-xl bg-muted px-4 py-3 h-14",
|
|
389
|
+
aboutToCancel && "bg-destructive/10"
|
|
390
|
+
),
|
|
391
|
+
children: [
|
|
392
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
393
|
+
/* @__PURE__ */ jsx(RecordingIndicator, {}),
|
|
394
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm tabular-nums text-neutral-foreground", children: formatDuration(duration) })
|
|
395
|
+
] }),
|
|
396
|
+
/* @__PURE__ */ jsxs(
|
|
397
|
+
"div",
|
|
398
|
+
{
|
|
399
|
+
className: cn(
|
|
400
|
+
"flex items-center gap-1 text-xs",
|
|
401
|
+
aboutToCancel ? "text-destructive font-medium" : "text-muted-foreground"
|
|
402
|
+
),
|
|
403
|
+
children: [
|
|
404
|
+
/* @__PURE__ */ jsx(ChevronLeft, { className: "size-3" }),
|
|
405
|
+
/* @__PURE__ */ jsx("span", { children: "Deslize para cancelar" })
|
|
406
|
+
]
|
|
407
|
+
}
|
|
408
|
+
)
|
|
409
|
+
]
|
|
410
|
+
}
|
|
411
|
+
),
|
|
412
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
413
|
+
/* @__PURE__ */ jsxs(
|
|
414
|
+
"div",
|
|
415
|
+
{
|
|
416
|
+
className: cn(
|
|
417
|
+
"absolute left-1/2 -translate-x-1/2 bottom-full mb-2 flex flex-col items-center gap-3 rounded-t-full bg-muted h-20 w-8 py-3 transition-transform",
|
|
418
|
+
aboutToLock && "scale-125 ring-2 ring-[#0EA460]"
|
|
419
|
+
),
|
|
420
|
+
"aria-hidden": "true",
|
|
421
|
+
children: [
|
|
422
|
+
/* @__PURE__ */ jsx(Lock, { className: cn("size-3", aboutToLock ? "text-[#0EA460]" : "text-muted-foreground") }),
|
|
423
|
+
/* @__PURE__ */ jsx(ChevronUp, { className: "size-3 text-muted-foreground animate-bounce" })
|
|
424
|
+
]
|
|
425
|
+
}
|
|
426
|
+
),
|
|
427
|
+
/* @__PURE__ */ jsx(
|
|
428
|
+
MicButton,
|
|
429
|
+
{
|
|
430
|
+
variant: "large",
|
|
431
|
+
pressed: true,
|
|
432
|
+
ariaLabel: "Gravando \u2014 arraste para cima para travar, para esquerda para cancelar",
|
|
433
|
+
className: cn(aboutToCancel && "animate-shake"),
|
|
434
|
+
style: aboutToCancel ? void 0 : { transform: `translate(${micTranslateX}px, ${micTranslateY}px)` }
|
|
435
|
+
}
|
|
436
|
+
)
|
|
437
|
+
] })
|
|
438
|
+
] })
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
function MessageBar(_a) {
|
|
444
|
+
var _b = _a, {
|
|
445
|
+
state = "default",
|
|
446
|
+
value = "",
|
|
447
|
+
onChange,
|
|
448
|
+
onSendText,
|
|
449
|
+
onStartRecording,
|
|
450
|
+
onPauseRecording,
|
|
451
|
+
onResumeRecording,
|
|
452
|
+
onCancelRecording,
|
|
453
|
+
onSendAudio,
|
|
454
|
+
onTogglePlay,
|
|
455
|
+
recordingDuration = 0,
|
|
456
|
+
isPlaying = false,
|
|
457
|
+
recordingStream,
|
|
458
|
+
playbackProgress = 0,
|
|
459
|
+
onSeekPlayback,
|
|
460
|
+
placeholder = "Digite sua mensagem...",
|
|
461
|
+
className
|
|
462
|
+
} = _b, props = __objRest(_b, [
|
|
463
|
+
"state",
|
|
464
|
+
"value",
|
|
465
|
+
"onChange",
|
|
466
|
+
"onSendText",
|
|
467
|
+
"onStartRecording",
|
|
468
|
+
"onPauseRecording",
|
|
469
|
+
"onResumeRecording",
|
|
470
|
+
"onCancelRecording",
|
|
471
|
+
"onSendAudio",
|
|
472
|
+
"onTogglePlay",
|
|
473
|
+
"recordingDuration",
|
|
474
|
+
"isPlaying",
|
|
475
|
+
"recordingStream",
|
|
476
|
+
"playbackProgress",
|
|
477
|
+
"onSeekPlayback",
|
|
478
|
+
"placeholder",
|
|
479
|
+
"className"
|
|
480
|
+
]);
|
|
481
|
+
const samplesRef = React.useRef([]);
|
|
482
|
+
const prevStateRef = React.useRef(state);
|
|
483
|
+
const lastBaseStateRef = React.useRef(
|
|
484
|
+
state === "audio-only" ? "audio-only" : "default"
|
|
485
|
+
);
|
|
486
|
+
React.useEffect(() => {
|
|
487
|
+
const prev = prevStateRef.current;
|
|
488
|
+
if (state === "recording" && prev !== "paused" && prev !== "recording") {
|
|
489
|
+
samplesRef.current = [];
|
|
490
|
+
}
|
|
491
|
+
if (state === "default" || state === "audio-only") {
|
|
492
|
+
lastBaseStateRef.current = state;
|
|
493
|
+
}
|
|
494
|
+
prevStateRef.current = state;
|
|
495
|
+
}, [state]);
|
|
496
|
+
const [isPressed, setIsPressed] = React.useState(false);
|
|
497
|
+
const [pressDelta, setPressDelta] = React.useState({ x: 0, y: 0 });
|
|
498
|
+
const pressDeltaRef = React.useRef({ x: 0, y: 0 });
|
|
499
|
+
const pressInfoRef = React.useRef({ startX: 0, startY: 0, startTime: 0, locked: false });
|
|
500
|
+
const callbacksRef = React.useRef({ onStartRecording, onCancelRecording, onSendAudio });
|
|
501
|
+
React.useEffect(() => {
|
|
502
|
+
callbacksRef.current = { onStartRecording, onCancelRecording, onSendAudio };
|
|
503
|
+
});
|
|
504
|
+
const updateDelta = (dx, dy) => {
|
|
505
|
+
pressDeltaRef.current = { x: dx, y: dy };
|
|
506
|
+
setPressDelta({ x: dx, y: dy });
|
|
507
|
+
};
|
|
508
|
+
const triggerHaptic = (ms = 50) => {
|
|
509
|
+
if (typeof navigator !== "undefined" && typeof navigator.vibrate === "function") {
|
|
510
|
+
try {
|
|
511
|
+
navigator.vibrate(ms);
|
|
512
|
+
} catch (e) {
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
React.useEffect(() => {
|
|
517
|
+
if (!isPressed) return;
|
|
518
|
+
const onMove = (e) => {
|
|
519
|
+
if (pressInfoRef.current.locked) return;
|
|
520
|
+
const { startX, startY } = pressInfoRef.current;
|
|
521
|
+
const dx = e.clientX - startX;
|
|
522
|
+
const dy = e.clientY - startY;
|
|
523
|
+
updateDelta(dx, dy);
|
|
524
|
+
if (dy <= -60) {
|
|
525
|
+
pressInfoRef.current.locked = true;
|
|
526
|
+
triggerHaptic(50);
|
|
527
|
+
setIsPressed(false);
|
|
528
|
+
updateDelta(0, 0);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
const onEnd = () => {
|
|
532
|
+
var _a2, _b2, _c, _d;
|
|
533
|
+
if (pressInfoRef.current.locked) return;
|
|
534
|
+
const { startTime } = pressInfoRef.current;
|
|
535
|
+
const elapsed = Date.now() - startTime;
|
|
536
|
+
const { x: dx } = pressDeltaRef.current;
|
|
537
|
+
setIsPressed(false);
|
|
538
|
+
updateDelta(0, 0);
|
|
539
|
+
if (dx <= -80 || elapsed < MIN_DURATION) {
|
|
540
|
+
triggerHaptic(50);
|
|
541
|
+
(_b2 = (_a2 = callbacksRef.current).onCancelRecording) == null ? void 0 : _b2.call(_a2);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
(_d = (_c = callbacksRef.current).onSendAudio) == null ? void 0 : _d.call(_c);
|
|
545
|
+
};
|
|
546
|
+
window.addEventListener("pointermove", onMove);
|
|
547
|
+
window.addEventListener("pointerup", onEnd);
|
|
548
|
+
window.addEventListener("pointercancel", onEnd);
|
|
549
|
+
return () => {
|
|
550
|
+
window.removeEventListener("pointermove", onMove);
|
|
551
|
+
window.removeEventListener("pointerup", onEnd);
|
|
552
|
+
window.removeEventListener("pointercancel", onEnd);
|
|
553
|
+
};
|
|
554
|
+
}, [isPressed]);
|
|
555
|
+
const handlePressStart = (e) => {
|
|
556
|
+
if (e.pointerType !== "touch") {
|
|
557
|
+
onStartRecording == null ? void 0 : onStartRecording();
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
pressInfoRef.current = {
|
|
561
|
+
startX: e.clientX,
|
|
562
|
+
startY: e.clientY,
|
|
563
|
+
startTime: Date.now(),
|
|
564
|
+
locked: false
|
|
565
|
+
};
|
|
566
|
+
updateDelta(0, 0);
|
|
567
|
+
setIsPressed(true);
|
|
568
|
+
onStartRecording == null ? void 0 : onStartRecording();
|
|
569
|
+
};
|
|
570
|
+
const pressHandlers = {
|
|
571
|
+
onPointerDown: handlePressStart
|
|
572
|
+
};
|
|
573
|
+
const handleKeyDown = (e) => {
|
|
574
|
+
if (e.key === "Enter" && !e.shiftKey && value.trim()) {
|
|
575
|
+
e.preventDefault();
|
|
576
|
+
onSendText == null ? void 0 : onSendText(value);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
if (state === "audio-only") {
|
|
580
|
+
return /* @__PURE__ */ jsx(
|
|
581
|
+
"div",
|
|
582
|
+
__spreadProps(__spreadValues({
|
|
583
|
+
"data-slot": "message-bar",
|
|
584
|
+
"data-state": "audio-only",
|
|
585
|
+
className: cn("flex items-center justify-center p-2", className)
|
|
586
|
+
}, props), {
|
|
587
|
+
children: /* @__PURE__ */ jsx(MicButton, __spreadValues({}, pressHandlers))
|
|
588
|
+
})
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
if (state === "recording" && isPressed) {
|
|
592
|
+
return /* @__PURE__ */ jsx(
|
|
593
|
+
PressedRecordingOverlay,
|
|
594
|
+
{
|
|
595
|
+
variant: lastBaseStateRef.current,
|
|
596
|
+
duration: recordingDuration,
|
|
597
|
+
deltaX: pressDelta.x,
|
|
598
|
+
deltaY: pressDelta.y
|
|
599
|
+
}
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
if (state === "recording") {
|
|
603
|
+
return /* @__PURE__ */ jsx(
|
|
604
|
+
"div",
|
|
605
|
+
__spreadProps(__spreadValues({
|
|
606
|
+
"data-slot": "message-bar",
|
|
607
|
+
"data-state": "recording",
|
|
608
|
+
className: cn(
|
|
609
|
+
"flex items-center gap-2 p-2 sm:gap-3 sm:p-0",
|
|
610
|
+
className
|
|
611
|
+
)
|
|
612
|
+
}, props), {
|
|
613
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 w-full sm:contents", children: [
|
|
614
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 sm:contents", children: [
|
|
615
|
+
/* @__PURE__ */ jsx("div", { className: "hidden sm:contents", children: /* @__PURE__ */ jsx(
|
|
616
|
+
IconButton,
|
|
617
|
+
{
|
|
618
|
+
onClick: onCancelRecording,
|
|
619
|
+
icon: Trash2,
|
|
620
|
+
ariaLabel: "Cancelar gravacao"
|
|
621
|
+
}
|
|
622
|
+
) }),
|
|
623
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-1 rounded-2xl bg-muted px-4 py-3", children: [
|
|
624
|
+
/* @__PURE__ */ jsx(RecordingIndicator, {}),
|
|
625
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm text-neutral-foreground tabular-nums", children: formatDuration(recordingDuration) }),
|
|
626
|
+
/* @__PURE__ */ jsx(LiveWaveform, { stream: recordingStream, samplesRef }),
|
|
627
|
+
/* @__PURE__ */ jsx("div", { className: "hidden sm:contents", children: /* @__PURE__ */ jsx(
|
|
628
|
+
"button",
|
|
629
|
+
{
|
|
630
|
+
type: "button",
|
|
631
|
+
onClick: onPauseRecording,
|
|
632
|
+
className: "shrink-0 inline-flex items-center justify-center text-neutral-foreground hover:opacity-80 transition-opacity",
|
|
633
|
+
"aria-label": "Pausar gravacao",
|
|
634
|
+
children: /* @__PURE__ */ jsx(Pause, { className: "size-5", fill: "currentColor", strokeWidth: 0 })
|
|
635
|
+
}
|
|
636
|
+
) })
|
|
637
|
+
] }),
|
|
638
|
+
/* @__PURE__ */ jsx("div", { className: "hidden sm:contents", children: /* @__PURE__ */ jsx(SendButton, { onClick: onSendAudio, ariaLabel: "Enviar audio" }) })
|
|
639
|
+
] }),
|
|
640
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between sm:hidden", children: [
|
|
641
|
+
/* @__PURE__ */ jsx(
|
|
642
|
+
IconButton,
|
|
643
|
+
{
|
|
644
|
+
onClick: onCancelRecording,
|
|
645
|
+
icon: Trash2,
|
|
646
|
+
ariaLabel: "Cancelar gravacao"
|
|
647
|
+
}
|
|
648
|
+
),
|
|
649
|
+
/* @__PURE__ */ jsx(
|
|
650
|
+
"button",
|
|
651
|
+
{
|
|
652
|
+
type: "button",
|
|
653
|
+
onClick: onPauseRecording,
|
|
654
|
+
className: "shrink-0 inline-flex items-center justify-center size-10 text-neutral-foreground hover:opacity-80 transition-opacity",
|
|
655
|
+
"aria-label": "Pausar gravacao",
|
|
656
|
+
children: /* @__PURE__ */ jsx(Pause, { className: "size-5", fill: "currentColor", strokeWidth: 0 })
|
|
657
|
+
}
|
|
658
|
+
),
|
|
659
|
+
/* @__PURE__ */ jsx(SendButton, { onClick: onSendAudio, ariaLabel: "Enviar audio" })
|
|
660
|
+
] })
|
|
661
|
+
] })
|
|
662
|
+
})
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
if (state === "paused") {
|
|
666
|
+
return /* @__PURE__ */ jsx(
|
|
667
|
+
"div",
|
|
668
|
+
__spreadProps(__spreadValues({
|
|
669
|
+
"data-slot": "message-bar",
|
|
670
|
+
"data-state": "paused",
|
|
671
|
+
className: cn(
|
|
672
|
+
"flex items-center gap-2 p-2 sm:gap-3 sm:p-0",
|
|
673
|
+
className
|
|
674
|
+
)
|
|
675
|
+
}, props), {
|
|
676
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 w-full sm:contents", children: [
|
|
677
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 sm:contents", children: [
|
|
678
|
+
/* @__PURE__ */ jsx("div", { className: "hidden sm:contents", children: /* @__PURE__ */ jsx(
|
|
679
|
+
IconButton,
|
|
680
|
+
{
|
|
681
|
+
onClick: onCancelRecording,
|
|
682
|
+
icon: Trash2,
|
|
683
|
+
ariaLabel: "Cancelar gravacao"
|
|
684
|
+
}
|
|
685
|
+
) }),
|
|
686
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-1 rounded-2xl bg-muted px-4 py-3", children: [
|
|
687
|
+
/* @__PURE__ */ jsx(
|
|
688
|
+
"button",
|
|
689
|
+
{
|
|
690
|
+
type: "button",
|
|
691
|
+
onClick: onTogglePlay,
|
|
692
|
+
className: "shrink-0 inline-flex items-center justify-center text-neutral-foreground hover:opacity-80 transition-opacity",
|
|
693
|
+
"aria-label": isPlaying ? "Pausar" : "Reproduzir",
|
|
694
|
+
children: isPlaying ? /* @__PURE__ */ jsx(Pause, { className: "size-5", fill: "currentColor", strokeWidth: 0 }) : /* @__PURE__ */ jsx(Play, { className: "size-5", fill: "currentColor", strokeWidth: 0 })
|
|
695
|
+
}
|
|
696
|
+
),
|
|
697
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm text-neutral-foreground tabular-nums", children: formatDuration(recordingDuration) }),
|
|
698
|
+
/* @__PURE__ */ jsx(
|
|
699
|
+
RecordedWaveform,
|
|
700
|
+
{
|
|
701
|
+
samples: samplesRef.current,
|
|
702
|
+
progress: playbackProgress,
|
|
703
|
+
onSeek: onSeekPlayback
|
|
704
|
+
}
|
|
705
|
+
),
|
|
706
|
+
/* @__PURE__ */ jsx("div", { className: "hidden sm:contents", children: /* @__PURE__ */ jsx(
|
|
707
|
+
"button",
|
|
708
|
+
{
|
|
709
|
+
type: "button",
|
|
710
|
+
onClick: onResumeRecording,
|
|
711
|
+
className: "shrink-0 inline-flex items-center justify-center text-neutral-foreground hover:opacity-80 transition-opacity",
|
|
712
|
+
"aria-label": "Continuar gravacao",
|
|
713
|
+
children: /* @__PURE__ */ jsx(Mic, { className: "size-5" })
|
|
714
|
+
}
|
|
715
|
+
) })
|
|
716
|
+
] }),
|
|
717
|
+
/* @__PURE__ */ jsx("div", { className: "hidden sm:contents", children: /* @__PURE__ */ jsx(SendButton, { onClick: onSendAudio, ariaLabel: "Enviar audio" }) })
|
|
718
|
+
] }),
|
|
719
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between sm:hidden", children: [
|
|
720
|
+
/* @__PURE__ */ jsx(
|
|
721
|
+
IconButton,
|
|
722
|
+
{
|
|
723
|
+
onClick: onCancelRecording,
|
|
724
|
+
icon: Trash2,
|
|
725
|
+
ariaLabel: "Cancelar gravacao"
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
/* @__PURE__ */ jsx(
|
|
729
|
+
"button",
|
|
730
|
+
{
|
|
731
|
+
type: "button",
|
|
732
|
+
onClick: onResumeRecording,
|
|
733
|
+
className: "shrink-0 inline-flex items-center justify-center size-10 text-neutral-foreground hover:opacity-80 transition-opacity",
|
|
734
|
+
"aria-label": "Continuar gravacao",
|
|
735
|
+
children: /* @__PURE__ */ jsx(Mic, { className: "size-5" })
|
|
736
|
+
}
|
|
737
|
+
),
|
|
738
|
+
/* @__PURE__ */ jsx(SendButton, { onClick: onSendAudio, ariaLabel: "Enviar audio" })
|
|
739
|
+
] })
|
|
740
|
+
] })
|
|
741
|
+
})
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
const isDisabled = state === "disabled";
|
|
745
|
+
const isActive = state === "active" || state === "default" && value.trim().length > 0;
|
|
746
|
+
return /* @__PURE__ */ jsxs(
|
|
747
|
+
"div",
|
|
748
|
+
__spreadProps(__spreadValues({
|
|
749
|
+
"data-slot": "message-bar",
|
|
750
|
+
"data-state": state,
|
|
751
|
+
className: cn(
|
|
752
|
+
"flex items-center gap-2 rounded-2xl bg-muted p-2",
|
|
753
|
+
className
|
|
754
|
+
)
|
|
755
|
+
}, props), {
|
|
756
|
+
children: [
|
|
757
|
+
/* @__PURE__ */ jsx(
|
|
758
|
+
"input",
|
|
759
|
+
{
|
|
760
|
+
type: "text",
|
|
761
|
+
value,
|
|
762
|
+
onChange: (e) => onChange == null ? void 0 : onChange(e.target.value),
|
|
763
|
+
onKeyDown: handleKeyDown,
|
|
764
|
+
disabled: isDisabled,
|
|
765
|
+
placeholder,
|
|
766
|
+
className: cn(
|
|
767
|
+
"flex-1 min-w-0 bg-transparent px-3 py-2 text-base text-neutral-foreground placeholder:text-neutral-muted-foreground outline-none",
|
|
768
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
769
|
+
),
|
|
770
|
+
"aria-label": "Mensagem"
|
|
771
|
+
}
|
|
772
|
+
),
|
|
773
|
+
isActive ? /* @__PURE__ */ jsx(
|
|
774
|
+
SendButton,
|
|
775
|
+
{
|
|
776
|
+
onClick: () => value.trim() && (onSendText == null ? void 0 : onSendText(value)),
|
|
777
|
+
disabled: !value.trim()
|
|
778
|
+
}
|
|
779
|
+
) : /* @__PURE__ */ jsx(MicButton, __spreadValues({}, pressHandlers))
|
|
780
|
+
]
|
|
781
|
+
})
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
export { MessageBar };
|
|
786
|
+
//# sourceMappingURL=chunk-PW464XEP.js.map
|
|
787
|
+
//# sourceMappingURL=chunk-PW464XEP.js.map
|