@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.
Files changed (39) hide show
  1. package/bin/mcp.mjs +150 -0
  2. package/dist/chunk-37C2K2NM.js +101 -0
  3. package/dist/chunk-37C2K2NM.js.map +1 -0
  4. package/dist/chunk-I5A3CME4.js +84 -0
  5. package/dist/chunk-I5A3CME4.js.map +1 -0
  6. package/dist/chunk-JRJBKPXW.js +118 -0
  7. package/dist/chunk-JRJBKPXW.js.map +1 -0
  8. package/dist/{chunk-PCLVQPIG.js → chunk-L2DJS6PG.js} +3 -3
  9. package/dist/{chunk-PCLVQPIG.js.map → chunk-L2DJS6PG.js.map} +1 -1
  10. package/dist/chunk-PW464XEP.js +787 -0
  11. package/dist/chunk-PW464XEP.js.map +1 -0
  12. package/dist/chunk-QANROH2K.js +50 -0
  13. package/dist/chunk-QANROH2K.js.map +1 -0
  14. package/dist/chunk-YMWRR7ET.js +503 -0
  15. package/dist/chunk-YMWRR7ET.js.map +1 -0
  16. package/dist/index.d.ts +8 -1
  17. package/dist/index.js +18 -12
  18. package/dist/layout/container.d.ts +24 -0
  19. package/dist/layout/container.js +5 -0
  20. package/dist/layout/container.js.map +1 -0
  21. package/dist/layout/grid.d.ts +26 -0
  22. package/dist/layout/grid.js +5 -0
  23. package/dist/layout/grid.js.map +1 -0
  24. package/dist/layout/stack.d.ts +47 -0
  25. package/dist/layout/stack.js +5 -0
  26. package/dist/layout/stack.js.map +1 -0
  27. package/dist/types-CVdl5nka.d.ts +9 -0
  28. package/dist/ui/chat-message.d.ts +57 -0
  29. package/dist/ui/chat-message.js +11 -0
  30. package/dist/ui/chat-message.js.map +1 -0
  31. package/dist/ui/chat-panel.js +1 -1
  32. package/dist/ui/live-waiting.js +2 -2
  33. package/dist/ui/message-bar.d.ts +69 -0
  34. package/dist/ui/message-bar.js +5 -0
  35. package/dist/ui/message-bar.js.map +1 -0
  36. package/dist/ui/message-rating.d.ts +29 -0
  37. package/dist/ui/message-rating.js +5 -0
  38. package/dist/ui/message-rating.js.map +1 -0
  39. 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