@fluencypassdevs/cycle 1.9.7 → 1.13.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 +80 -7
- package/dist/{chunk-37C2K2NM.js → chunk-6OYSTCGP.js} +5 -4
- package/dist/chunk-6OYSTCGP.js.map +1 -0
- package/dist/{chunk-YMWRR7ET.js → chunk-CG7NXMBC.js} +18 -23
- package/dist/chunk-CG7NXMBC.js.map +1 -0
- package/dist/{chunk-27PO7X4G.js → chunk-CWMXYPWK.js} +101 -11
- package/dist/chunk-CWMXYPWK.js.map +1 -0
- package/dist/chunk-JGUDRAWA.js +486 -0
- package/dist/chunk-JGUDRAWA.js.map +1 -0
- package/dist/composites/chat-thread.d.ts +142 -0
- package/dist/composites/chat-thread.js +15 -0
- package/dist/composites/chat-thread.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10 -9
- package/dist/ui/chat-message.d.ts +7 -2
- package/dist/ui/chat-message.js +1 -1
- package/dist/ui/message-bar.d.ts +24 -1
- package/dist/ui/message-bar.js +1 -1
- package/dist/ui/message-rating.d.ts +2 -1
- package/dist/ui/message-rating.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-27PO7X4G.js.map +0 -1
- package/dist/chunk-37C2K2NM.js.map +0 -1
- package/dist/chunk-YMWRR7ET.js.map +0 -1
package/bin/mcp.mjs
CHANGED
|
@@ -411,11 +411,11 @@ const COMPONENTS = [
|
|
|
411
411
|
{
|
|
412
412
|
name: "ChatMessage",
|
|
413
413
|
import: `import { ChatMessage } from "@fluencypassdevs/cycle"`,
|
|
414
|
-
description: "Mensagem de conversa com IA. Persona ai/user/system, suporta texto + audio inline + like/dislike. Loading state com typing indicator. AI usa icone Ai + bubble transparente. User usa bubble pink coral (theme-brand) + avatar.",
|
|
414
|
+
description: "Mensagem de conversa com IA. Persona ai/user/system, suporta texto + audio inline + like/dislike. Loading state com typing indicator. AI usa icone Ai + bubble transparente. User usa bubble pink coral (theme-brand) + avatar. CONVENCAO DO PRODUTO: (1) toda msg da IA com `audioSrc` deve ter `text` (roteiro/legenda do TTS); (2) toda msg do user com `audioSrc` deve ter `text` (transcricao gerada por IA). Garante acessibilidade (leitura + escuta), busca no historico e suporte a screen readers.",
|
|
415
415
|
props: [
|
|
416
416
|
{ name: "persona", type: '"ai" | "user" | "system"', default: "-" },
|
|
417
|
-
{ name: "text", type: "string", default: "-" },
|
|
418
|
-
{ name: "audioSrc", type: "string", default: "-" },
|
|
417
|
+
{ name: "text", type: "string", default: "-", note: "Em ai com audioSrc, e o roteiro do TTS. Em user com audioSrc, e a transcricao." },
|
|
418
|
+
{ name: "audioSrc", type: "string", default: "-", note: "IA: TTS gerado. User: gravacao do microfone. Em ambos, sempre acompanhe com text." },
|
|
419
419
|
{ name: "avatar", type: "string", default: "-" },
|
|
420
420
|
{ name: "author", type: "string", default: "-" },
|
|
421
421
|
{ name: "showFeedback", type: "boolean", default: "false" },
|
|
@@ -423,8 +423,11 @@ const COMPONENTS = [
|
|
|
423
423
|
{ name: "onFeedbackChange", type: "(val) => void", default: "-" },
|
|
424
424
|
{ name: "loading", type: "boolean", default: "false" },
|
|
425
425
|
],
|
|
426
|
-
example:
|
|
427
|
-
<ChatMessage persona="ai" text="
|
|
426
|
+
example: `{/* IA: sempre com text + audioSrc (convencao) */}
|
|
427
|
+
<ChatMessage persona="ai" text="Como posso ajudar?" audioSrc="/ai-tts.mp3" showFeedback />
|
|
428
|
+
{/* User com audio: SEMPRE inclua text com a transcricao */}
|
|
429
|
+
<ChatMessage persona="user" text="Can you explain present perfect?" audioSrc="/user-rec.mp3" author="Felipe" />
|
|
430
|
+
{/* User so com texto (digitado): audio nao se aplica */}
|
|
428
431
|
<ChatMessage persona="user" text="Oi" avatar="/me.jpg" author="Felipe" />
|
|
429
432
|
<ChatMessage persona="ai" loading />
|
|
430
433
|
<ChatMessage persona="system" text="Conversa iniciada" />`,
|
|
@@ -450,7 +453,7 @@ const COMPONENTS = [
|
|
|
450
453
|
{
|
|
451
454
|
name: "MessageBar",
|
|
452
455
|
import: `import { MessageBar } from "@fluencypassdevs/cycle"`,
|
|
453
|
-
description: "Input bar para chat com IA. 6 estados: default, audio-only, active, disabled, recording, paused. UI-only: consumer implementa MediaRecorder + HTMLAudioElement via callbacks. Live waveform real durante recording (Web Audio API, rolling window estilo WhatsApp). RecordedWaveform real durante paused com peaks + barras tocadas/nao-tocadas + dot verde + click/drag-to-seek. Press-to-record no mobile (touch-only): long-press inicia, drag-up trava em hands-free, drag-left cancela, release < 1s cancela, release >= 1s envia. Vibracao haptica em lock/cancel.",
|
|
456
|
+
description: "Input bar para chat com IA. 6 estados: default, audio-only, active, disabled, recording, paused. UI-only: consumer implementa MediaRecorder + HTMLAudioElement via callbacks. Live waveform real durante recording (Web Audio API, rolling window estilo WhatsApp). RecordedWaveform real durante paused com peaks + barras tocadas/nao-tocadas + dot verde + click/drag-to-seek. Press-to-record no mobile (touch-only): long-press inicia, drag-up trava em hands-free, drag-left cancela, release < 1s cancela, release >= 1s envia. Vibracao haptica em lock/cancel. Suporta limite de duracao via maxRecordingDuration (segundos) com warning visual configuravel nos segundos finais (timer destructive + 'Xs restantes' aria-live) e callback onMaxDurationReached ao atingir o limite.",
|
|
454
457
|
props: [
|
|
455
458
|
{ name: "state", type: '"default" | "audio-only" | "active" | "disabled" | "recording" | "paused"', default: '"default"' },
|
|
456
459
|
{ name: "value", type: "string", default: '""' },
|
|
@@ -468,6 +471,10 @@ const COMPONENTS = [
|
|
|
468
471
|
{ name: "playbackProgress", type: "number (0-1)", default: "0", note: "consumer atualiza via audio.ontimeupdate enquanto toca o preview no estado paused" },
|
|
469
472
|
{ name: "onSeekPlayback", type: "(progress) => void", default: "-", note: "consumer seta audio.currentTime = progress * duration" },
|
|
470
473
|
{ name: "placeholder", type: "string", default: '"Digite sua mensagem..."' },
|
|
474
|
+
{ name: "maxRecordingDuration", type: "number", default: "-", note: "limite em segundos. Quando atingido, dispara onMaxDurationReached uma vez. Sem isso, sem limite." },
|
|
475
|
+
{ name: "warnAtSecondsLeft", type: "number", default: "10", note: "ativa warning visual nos ultimos N segundos (timer destructive + 'Xs restantes' aria-live polite)" },
|
|
476
|
+
{ name: "onMaxDurationReached", type: "() => void", default: "-", note: "disparado uma vez ao atingir o limite. ChatThread usa pra auto-pausar; consumer puro pode usar pra customizar" },
|
|
477
|
+
{ name: "secondsLeftLabel", type: "(s) => string", default: "(s) => `${s}s restantes`", note: "label do warning (pra i18n)" },
|
|
471
478
|
],
|
|
472
479
|
example: `// 1) Captura: passa o MESMO stream pro MessageBar (live waveform).
|
|
473
480
|
// 2) Pause: recorder.stop() gera o Blob; crie um <audio> e atualize playbackProgress via ontimeupdate.
|
|
@@ -475,9 +482,75 @@ const COMPONENTS = [
|
|
|
475
482
|
|
|
476
483
|
<MessageBar value={text} onChange={setText} onSendText={(t) => send(t)} onStartRecording={() => startRec()} />
|
|
477
484
|
<MessageBar state="recording" recordingDuration={4} recordingStream={stream} onPauseRecording={pause} onCancelRecording={cancel} onSendAudio={send} />
|
|
478
|
-
<MessageBar state="paused" recordingDuration={42} isPlaying={isPlaying} playbackProgress={progress} onTogglePlay={togglePlay} onSeekPlayback={seek} onResumeRecording={resume} onCancelRecording={cancel} onSendAudio={send}
|
|
485
|
+
<MessageBar state="paused" recordingDuration={42} isPlaying={isPlaying} playbackProgress={progress} onTogglePlay={togglePlay} onSeekPlayback={seek} onResumeRecording={resume} onCancelRecording={cancel} onSendAudio={send} />
|
|
486
|
+
{/* Com limite de duracao (timer fica vermelho aos ultimos 5s, auto-pausa aos 30s) */}
|
|
487
|
+
<MessageBar state="recording" recordingDuration={duration} recordingStream={stream} maxRecordingDuration={30} warnAtSecondsLeft={5} onMaxDurationReached={() => pause()} onPauseRecording={pause} onSendAudio={send} />`,
|
|
479
488
|
keywords: ["chat", "input", "message", "bar", "audio", "recording", "microphone", "mensagem", "envio", "press-to-record", "waveform", "playback", "whatsapp"],
|
|
480
489
|
},
|
|
490
|
+
{
|
|
491
|
+
name: "ChatThread",
|
|
492
|
+
import: `import { ChatThread, type ChatThreadMessage } from "@fluencypassdevs/cycle"`,
|
|
493
|
+
description: "Composite 'tela de chat pronta' que junta ChatMessage + MessageBar + MessageRating. Gerencia state machine interno do MessageBar (default↔recording↔paused) baseado nos callbacks. Auto-scroll inteligente (preserva posicao se user subiu + botao '↓ nova mensagem'). Scroll infinito via onLoadMore. Banners configuraveis: offline, rateLimitedUntil (countdown), quotaExhausted (AlertDialog). Retry inline em msgs failed do user (B2) e regenerar resposta da IA (B3). Botao 'Parar resposta' durante thinking e OPT-IN via presenca de onStopResponse — sem callback, MessageBar some com animacao de saida e volta quando IA termina. requestRating per-message renderiza MessageRating inline embaixo da bubble da IA — escolha some imediato, callback onRate(messageId, value, label) permite consumer enviar como msg do user. initialThinking pra conversas que comecam com IA falando. Input controlled internamente. Animacoes fade+slide (200ms enter / 150ms leave). Suporta limite de duracao de gravacao via maxRecordingDuration (segundos) com warning visual configuravel e auto-pause ao atingir; override via onMaxDurationReached.",
|
|
494
|
+
props: [
|
|
495
|
+
{ name: "messages", type: "ChatThreadMessage[]", default: "[]", note: "array de mensagens da conversa" },
|
|
496
|
+
{ name: "state", type: '"idle" | "sending" | "thinking" | "error"', default: '"idle"', note: "estado global da conversa (controlled)" },
|
|
497
|
+
{ name: "initialThinking", type: "boolean", default: "false", note: "quando true e thread vazio, monta direto em thinking (cenario IA fala primeiro)" },
|
|
498
|
+
{ name: "audioOnlyMode", type: "boolean", default: "false", note: "MessageBar interno em modo apenas audio (sem input texto)" },
|
|
499
|
+
{ name: "onSendText", type: "(text) => void", default: "-" },
|
|
500
|
+
{ name: "onSendAudio", type: "() => void", default: "-", note: "dispara quando user confirma envio (consumer pega Blob do MediaRecorder proprio)" },
|
|
501
|
+
{ name: "onStartRecording", type: "() => void", default: "-", note: "consumer chama getUserMedia + cria MediaRecorder" },
|
|
502
|
+
{ name: "onPauseRecording", type: "() => void", default: "-", note: "consumer faz recorder.stop() + cria Blob para preview" },
|
|
503
|
+
{ name: "onResumeRecording", type: "() => void", default: "-" },
|
|
504
|
+
{ name: "onCancelRecording", type: "() => void", default: "-" },
|
|
505
|
+
{ name: "onTogglePlay", type: "() => void", default: "-" },
|
|
506
|
+
{ name: "onSeekPlayback", type: "(progress) => void", default: "-" },
|
|
507
|
+
{ name: "recordingStream", type: "MediaStream | null", default: "null", note: "pra live waveform durante recording" },
|
|
508
|
+
{ name: "recordingDuration", type: "number", default: "0" },
|
|
509
|
+
{ name: "isPlaying", type: "boolean", default: "false" },
|
|
510
|
+
{ name: "playbackProgress", type: "number (0-1)", default: "0" },
|
|
511
|
+
{ name: "onRetryMessage", type: "(messageId) => void", default: "-", note: "retry msg do user com status=failed" },
|
|
512
|
+
{ name: "onRegenerateResponse", type: "() => void", default: "-", note: "regerar resposta da IA com erro" },
|
|
513
|
+
{ name: "onStopResponse", type: "() => void", default: "-", note: "OPT-IN: passar este callback ativa o botao 'Parar resposta' durante thinking. Sem ele, MessageBar some com animacao e volta quando IA termina." },
|
|
514
|
+
{ name: "onLoadMore", type: "() => Promise<void>", default: "-", note: "scroll infinito ao chegar no topo (G1)" },
|
|
515
|
+
{ name: "offline", type: "boolean", default: "false", note: "banner topo + MessageBar disabled" },
|
|
516
|
+
{ name: "rateLimitedUntil", type: "number | null", default: "null", note: "timestamp epoch ate quando bloquear (banner countdown)" },
|
|
517
|
+
{ name: "quotaExhausted", type: "boolean", default: "false", note: "abre AlertDialog modal" },
|
|
518
|
+
{ name: "quotaExhaustedConfig", type: "{ title, description, ctaLabel, onCta }", default: "-", note: "conteudo do modal configuravel" },
|
|
519
|
+
{ name: "onRate", type: "(messageId, value, label) => void", default: "-", note: "rating de uma msg da IA com requestRating=true" },
|
|
520
|
+
{ name: "ratingLabels", type: "Partial<MessageRatingLabels>", default: "PT-BR defaults", note: "labels customizadas do rating" },
|
|
521
|
+
{ name: "placeholder", type: "string", default: '"Digite sua mensagem..."' },
|
|
522
|
+
{ name: "maxLength", type: "number", default: "-" },
|
|
523
|
+
{ name: "userAvatar", type: "string", default: "-", note: "URL do avatar do user — propagado pra todas as msgs do user automaticamente" },
|
|
524
|
+
{ name: "userName", type: "string", default: "-", note: "Nome do user — usado para iniciais no fallback do avatar e alt da imagem" },
|
|
525
|
+
{ name: "maxRecordingDuration", type: "number", default: "-", note: "limite de duracao da gravacao em segundos. Ao atingir, auto-pausa (vai pro preview) a menos que onMaxDurationReached seja passado" },
|
|
526
|
+
{ name: "warnAtSecondsLeft", type: "number", default: "10", note: "passthrough pro MessageBar — quando ativar o warning visual nos segundos finais" },
|
|
527
|
+
{ name: "onMaxDurationReached", type: "() => void", default: "-", note: "OVERRIDE do auto-pause. Quando passado, consumer assume controle ao atingir o limite" },
|
|
528
|
+
{ name: "secondsLeftLabel", type: "(s) => string", default: "(s) => `${s}s restantes`", note: "passthrough pro MessageBar (pra i18n)" },
|
|
529
|
+
],
|
|
530
|
+
example: `// ChatThreadMessage extra fields:
|
|
531
|
+
// - status?: "sent" | "pending" | "failed"
|
|
532
|
+
// - text?: string
|
|
533
|
+
// - audioSrc?: string (URL ou Blob URL)
|
|
534
|
+
// - audioPeaks?: number[]
|
|
535
|
+
// - errorText?: string (msg de erro custom para AI failed)
|
|
536
|
+
// - requestRating?: boolean (true na msg da IA que pede avaliacao — renderiza MessageRating inline)
|
|
537
|
+
|
|
538
|
+
<ChatThread
|
|
539
|
+
messages={messages}
|
|
540
|
+
state={state}
|
|
541
|
+
initialThinking
|
|
542
|
+
userAvatar="/me.jpg"
|
|
543
|
+
userName="Felipe Pereira"
|
|
544
|
+
onSendText={(text) => sendText(text)}
|
|
545
|
+
onSendAudio={() => sendAudio()}
|
|
546
|
+
onStartRecording={() => startRec()}
|
|
547
|
+
onPauseRecording={() => pauseRec()}
|
|
548
|
+
onRate={(msgId, value, label) => {
|
|
549
|
+
setMessages((m) => [...m, { id: newId(), persona: "user", status: "sent", text: label.subtitle }])
|
|
550
|
+
}}
|
|
551
|
+
/>`,
|
|
552
|
+
keywords: ["chat", "thread", "conversation", "conversa", "ai", "ia", "composite", "tela", "screen", "rating", "scroll", "thinking", "offline", "rate-limit", "quota", "feedback"],
|
|
553
|
+
},
|
|
481
554
|
{
|
|
482
555
|
name: "LikeDislike",
|
|
483
556
|
import: `import { LikeDislike } from "@fluencypassdevs/cycle"`,
|
|
@@ -3,13 +3,14 @@ import { __objRest, __spreadValues, __spreadProps } from './chunk-YINJ5YZ5.js';
|
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var DEFAULT_MESSAGE_RATING_LABELS = {
|
|
7
7
|
1: { emoji: "\u{1F621}", title: "Disappointing", subtitle: "Decepcionante" },
|
|
8
8
|
2: { emoji: "\u{1F61E}", title: "Frustrating", subtitle: "Frustrante" },
|
|
9
9
|
3: { emoji: "\u{1F610}", title: "Ok", subtitle: "Tudo bem" },
|
|
10
10
|
4: { emoji: "\u{1F642}", title: "Helpful", subtitle: "Ajudou" },
|
|
11
11
|
5: { emoji: "\u{1F60D}", title: "Loved it", subtitle: "Amei" }
|
|
12
12
|
};
|
|
13
|
+
var DEFAULT_LABELS = DEFAULT_MESSAGE_RATING_LABELS;
|
|
13
14
|
var VALUES = [1, 2, 3, 4, 5];
|
|
14
15
|
function MessageRating(_a) {
|
|
15
16
|
var _b = _a, {
|
|
@@ -96,6 +97,6 @@ function MessageRating(_a) {
|
|
|
96
97
|
);
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
export { MessageRating };
|
|
100
|
-
//# sourceMappingURL=chunk-
|
|
101
|
-
//# sourceMappingURL=chunk-
|
|
100
|
+
export { DEFAULT_MESSAGE_RATING_LABELS, MessageRating };
|
|
101
|
+
//# sourceMappingURL=chunk-6OYSTCGP.js.map
|
|
102
|
+
//# sourceMappingURL=chunk-6OYSTCGP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ui/message-rating.tsx"],"names":[],"mappings":"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","file":"chunk-6OYSTCGP.js","sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* ─── Types ───────────────────────────────────────────────────── */\n\nexport type MessageRatingValue = 1 | 2 | 3 | 4 | 5\n\nexport interface RatingLabel {\n emoji: string\n title: string\n subtitle: string\n}\n\nexport type MessageRatingLabels = Record<MessageRatingValue, RatingLabel>\n\nexport interface MessageRatingProps extends Omit<React.ComponentProps<\"div\">, \"onChange\" | \"defaultValue\"> {\n /** Valor selecionado (controlado) */\n value?: MessageRatingValue | null\n /** Valor inicial (nao controlado) */\n defaultValue?: MessageRatingValue | null\n /** Callback ao escolher uma opcao */\n onChange?: (value: MessageRatingValue) => void\n /** Layout. \"auto\" usa vertical (column) abaixo de sm e horizontal acima */\n layout?: \"horizontal\" | \"vertical\" | \"auto\"\n /** Labels customizadas (default: PT-BR) */\n labels?: Partial<MessageRatingLabels>\n /** Desabilita a interacao */\n disabled?: boolean\n /** Titulo opcional acima das opcoes */\n title?: string\n}\n\n/* ─── Default labels (PT-BR) ──────────────────────────────────── */\n\nexport const DEFAULT_MESSAGE_RATING_LABELS: MessageRatingLabels = {\n 1: { emoji: \"😡\", title: \"Disappointing\", subtitle: \"Decepcionante\" },\n 2: { emoji: \"😞\", title: \"Frustrating\", subtitle: \"Frustrante\" },\n 3: { emoji: \"😐\", title: \"Ok\", subtitle: \"Tudo bem\" },\n 4: { emoji: \"🙂\", title: \"Helpful\", subtitle: \"Ajudou\" },\n 5: { emoji: \"😍\", title: \"Loved it\", subtitle: \"Amei\" },\n}\n\n// Backwards-compatible alias usado internamente\nconst DEFAULT_LABELS = DEFAULT_MESSAGE_RATING_LABELS\n\nconst VALUES: MessageRatingValue[] = [1, 2, 3, 4, 5]\n\n/* ─── Component ───────────────────────────────────────────────── */\n\nfunction MessageRating({\n value: controlledValue,\n defaultValue,\n onChange,\n layout = \"auto\",\n labels: labelOverrides,\n disabled = false,\n title,\n className,\n ...props\n}: MessageRatingProps) {\n const [internalValue, setInternalValue] = React.useState<MessageRatingValue | null>(\n defaultValue ?? null\n )\n const isControlled = controlledValue !== undefined\n const value = isControlled ? controlledValue : internalValue\n\n const labels: MessageRatingLabels = React.useMemo(\n () => ({ ...DEFAULT_LABELS, ...labelOverrides }),\n [labelOverrides]\n )\n\n const handleSelect = (v: MessageRatingValue) => {\n if (disabled) return\n if (!isControlled) setInternalValue(v)\n onChange?.(v)\n }\n\n /* Layout: vertical (column) sobrescreve so essa direcao. horizontal/auto\n sao iguais agora (sempre flex-row, pois mobile e desktop diferem no\n conteudo de cada botao, nao na direcao do container). */\n const layoutClass = layout === \"vertical\" ? \"flex-col\" : \"flex-row\"\n\n return (\n <div\n data-slot=\"message-rating\"\n role=\"radiogroup\"\n aria-label={title ?? \"Avalie sua experiencia\"}\n className={cn(\"flex flex-col gap-3\", className)}\n {...props}\n >\n {title && (\n <p className=\"text-sm font-medium text-foreground\">{title}</p>\n )}\n <div className={cn(\"flex gap-2\", layoutClass)}>\n {VALUES.map((v) => {\n const label = labels[v]\n const selected = value === v\n return (\n <button\n key={v}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n aria-label={`${label.title} — ${label.subtitle}`}\n disabled={disabled}\n onClick={() => handleSelect(v)}\n className={cn(\n /* Base — shared mobile/desktop */\n \"flex items-center transition-[color,background-color,border-color,box-shadow] outline-none\",\n \"hover:opacity-90 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n /* Mobile: compacto, justa o emoji centralizado em pill cinza */\n \"rounded-xl px-3 py-2 justify-center\",\n /* Desktop: card completo com texto, flex-1, border */\n \"sm:flex-1 sm:gap-3 sm:px-3 sm:py-2.5 sm:text-left sm:justify-start sm:border\",\n /* Selected vs unselected (estados diferentes por breakpoint) */\n selected\n ? \"bg-accent theme-brand sm:border-primary sm:shadow-xs\"\n : \"bg-muted sm:bg-transparent sm:border-neutral-border\"\n )}\n >\n <span className=\"text-base sm:text-2xl leading-none shrink-0\" aria-hidden=\"true\">\n {label.emoji}\n </span>\n {/* Labels: visiveis apenas no desktop */}\n <span className=\"hidden sm:flex sm:flex-col min-w-0\">\n <span className=\"text-sm font-medium leading-tight text-foreground truncate\">\n {label.title}\n </span>\n <span className=\"text-xs text-muted-foreground leading-tight truncate\">\n {label.subtitle}\n </span>\n </span>\n </button>\n )\n })}\n </div>\n </div>\n )\n}\n\nexport { MessageRating }\n"]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { LikeDislike } from './chunk-F2XA2Z75.js';
|
|
2
2
|
import { Ai } from './chunk-JPEDYOV7.js';
|
|
3
|
+
import { CycleIcon } from './chunk-V7M2NHUO.js';
|
|
3
4
|
import { Avatar, AvatarImage, AvatarFallback } from './chunk-MSLQRGSP.js';
|
|
4
5
|
import { cn } from './chunk-TYCPXAXF.js';
|
|
5
6
|
import { __objRest, __spreadProps, __spreadValues } from './chunk-YINJ5YZ5.js';
|
|
6
7
|
import * as React from 'react';
|
|
7
8
|
import { cva } from 'class-variance-authority';
|
|
8
|
-
import { Pause, Play } from 'lucide-react';
|
|
9
|
+
import { Pause, Play, User } from 'lucide-react';
|
|
9
10
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
10
11
|
|
|
11
12
|
var chatMessageVariants = cva("flex w-full text-sm", {
|
|
@@ -143,8 +144,6 @@ var STATIC_FALLBACK_PEAKS = [
|
|
|
143
144
|
].map((h) => h / 32);
|
|
144
145
|
var MIN_BAR_HEIGHT = 2;
|
|
145
146
|
var MAX_BAR_HEIGHT = 32;
|
|
146
|
-
var BAR_WIDTH = 2;
|
|
147
|
-
var BAR_GAP = 2;
|
|
148
147
|
var SPEED_OPTIONS = [1, 1.25, 1.5, 2, 0.75];
|
|
149
148
|
var peaksCache = /* @__PURE__ */ new Map();
|
|
150
149
|
async function calculatePeaks(audioUrl, barCount) {
|
|
@@ -273,13 +272,10 @@ function ChatAudio(_a) {
|
|
|
273
272
|
if (!audio || !waveform2 || !duration) return;
|
|
274
273
|
const rect = waveform2.getBoundingClientRect();
|
|
275
274
|
const x = clientX - rect.left;
|
|
276
|
-
const newProgress = Math.max(
|
|
277
|
-
0,
|
|
278
|
-
Math.min(x / (peaks.length * (BAR_WIDTH + BAR_GAP) - BAR_GAP), 1)
|
|
279
|
-
);
|
|
275
|
+
const newProgress = Math.max(0, Math.min(x / rect.width, 1));
|
|
280
276
|
audio.currentTime = newProgress * duration;
|
|
281
277
|
},
|
|
282
|
-
[duration
|
|
278
|
+
[duration]
|
|
283
279
|
);
|
|
284
280
|
const handlePointerDown = (e) => {
|
|
285
281
|
if (!duration) return;
|
|
@@ -298,12 +294,11 @@ function ChatAudio(_a) {
|
|
|
298
294
|
};
|
|
299
295
|
const progress = duration > 0 ? currentTime / duration : 0;
|
|
300
296
|
const displayTime = formatAudioTime(currentTime);
|
|
301
|
-
const barsLayoutWidth = peaks.length * (BAR_WIDTH + BAR_GAP) - BAR_GAP;
|
|
302
297
|
const waveform = /* @__PURE__ */ jsxs(
|
|
303
298
|
"div",
|
|
304
299
|
{
|
|
305
300
|
ref: waveformRef,
|
|
306
|
-
className: "flex-1 relative h-8 min-w-0
|
|
301
|
+
className: "flex-1 relative h-8 min-w-0 touch-none cursor-pointer select-none",
|
|
307
302
|
onPointerDown: handlePointerDown,
|
|
308
303
|
onPointerMove: handlePointerMove,
|
|
309
304
|
onPointerUp: handlePointerUp,
|
|
@@ -314,7 +309,7 @@ function ChatAudio(_a) {
|
|
|
314
309
|
"aria-valuemax": Math.round(duration),
|
|
315
310
|
"aria-valuenow": Math.round(currentTime),
|
|
316
311
|
children: [
|
|
317
|
-
/* @__PURE__ */ jsx("div", { className: "absolute inset-
|
|
312
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-between", children: peaks.map((peak, i) => {
|
|
318
313
|
const isPlayed = i / peaks.length <= progress;
|
|
319
314
|
const height = Math.max(
|
|
320
315
|
MIN_BAR_HEIGHT,
|
|
@@ -336,7 +331,7 @@ function ChatAudio(_a) {
|
|
|
336
331
|
"div",
|
|
337
332
|
{
|
|
338
333
|
className: "absolute top-1/2 z-10 -translate-y-1/2 -translate-x-1/2 size-3 rounded-full bg-[#098A5E] pointer-events-none shadow-md",
|
|
339
|
-
style: { left: `${progress *
|
|
334
|
+
style: { left: `${progress * 100}%` },
|
|
340
335
|
"aria-hidden": "true"
|
|
341
336
|
}
|
|
342
337
|
)
|
|
@@ -453,11 +448,11 @@ function ChatMessage(_a) {
|
|
|
453
448
|
children: /* @__PURE__ */ jsxs("div", { className: "flex gap-3 max-w-[85%] sm:max-w-[75%] w-full", children: [
|
|
454
449
|
/* @__PURE__ */ jsx("div", { className: "hidden sm:flex shrink-0 size-8 rounded-md bg-primary items-center justify-center theme-brand", children: /* @__PURE__ */ jsx("span", { className: "text-primary-foreground", children: /* @__PURE__ */ jsx(Ai, { size: "sm", decorative: true }) }) }),
|
|
455
450
|
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 min-w-0 flex-1", children: loading ? (
|
|
456
|
-
/* Loading: dots dentro de bubble cinza (speech bubble shape — top-left reto) */
|
|
457
|
-
/* @__PURE__ */ jsx("div", { className: "self-start inline-flex items-center rounded-2xl
|
|
451
|
+
/* Loading: dots dentro de bubble cinza (speech bubble shape — top-left reto em todos os breakpoints) */
|
|
452
|
+
/* @__PURE__ */ jsx("div", { className: "self-start inline-flex items-center rounded-2xl rounded-tl-none bg-muted p-5 text-muted-foreground", children: /* @__PURE__ */ jsx(TypingDots, {}) })
|
|
458
453
|
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
459
|
-
text && /* Text bubble: bg-muted sempre, speech bubble shape (tl-none)
|
|
460
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-2xl bg-muted p-5
|
|
454
|
+
text && /* Text bubble: bg-muted sempre, speech bubble shape (tl-none) em todos os breakpoints */
|
|
455
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-2xl rounded-tl-none bg-muted p-5", children: /* @__PURE__ */ jsx("p", { className: "text-base leading-6 text-neutral-foreground break-words", children: text }) }),
|
|
461
456
|
audioSrc && /* @__PURE__ */ jsx(ChatAudio, { src: audioSrc, variant: "neutral" }),
|
|
462
457
|
showFeedback && /* @__PURE__ */ jsx(
|
|
463
458
|
LikeDislike,
|
|
@@ -481,17 +476,17 @@ function ChatMessage(_a) {
|
|
|
481
476
|
}, props), {
|
|
482
477
|
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 max-w-[85%] sm:max-w-[75%] w-full justify-end", children: [
|
|
483
478
|
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 min-w-0 items-end theme-brand flex-1", children: loading ? (
|
|
484
|
-
/* Loading: dots dentro de bubble pink (speech bubble shape — top-right reto) */
|
|
485
|
-
/* @__PURE__ */ jsx("div", { className: "inline-flex items-center rounded-2xl
|
|
479
|
+
/* Loading: dots dentro de bubble pink (speech bubble shape — top-right reto em todos os breakpoints) */
|
|
480
|
+
/* @__PURE__ */ jsx("div", { className: "inline-flex items-center rounded-2xl rounded-tr-none bg-accent p-5 text-neutral-muted-foreground", children: /* @__PURE__ */ jsx(TypingDots, {}) })
|
|
486
481
|
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
487
|
-
text && /* Text bubble: speech bubble shape (top-right reto)
|
|
482
|
+
text && /* Text bubble: speech bubble shape (top-right reto) em todos os breakpoints.
|
|
488
483
|
text-neutral-foreground = preto neutro fixo, nao muda com theme-brand */
|
|
489
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-2xl
|
|
484
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-2xl rounded-tr-none bg-accent p-5", children: /* @__PURE__ */ jsx("p", { className: "text-base leading-6 text-neutral-foreground break-words", children: text }) }),
|
|
490
485
|
audioSrc && /* @__PURE__ */ jsx(ChatAudio, { src: audioSrc, variant: "brand" })
|
|
491
486
|
] }) }),
|
|
492
487
|
/* @__PURE__ */ jsxs(Avatar, { size: "lg", className: "hidden sm:flex shrink-0 rounded-lg", children: [
|
|
493
488
|
avatar && /* @__PURE__ */ jsx(AvatarImage, { src: avatar, alt: author != null ? author : "User", className: "rounded-lg" }),
|
|
494
|
-
/* @__PURE__ */ jsx(AvatarFallback, { className: "rounded-lg bg-muted text-muted-foreground text-sm", children: getInitials(author) })
|
|
489
|
+
/* @__PURE__ */ jsx(AvatarFallback, { className: "rounded-lg bg-muted text-muted-foreground text-sm", children: author ? getInitials(author) : /* @__PURE__ */ jsx(CycleIcon, { icon: User, size: "sm", decorative: true }) })
|
|
495
490
|
] })
|
|
496
491
|
] })
|
|
497
492
|
})
|
|
@@ -499,5 +494,5 @@ function ChatMessage(_a) {
|
|
|
499
494
|
}
|
|
500
495
|
|
|
501
496
|
export { ChatAudio, ChatMessage, chatMessageVariants };
|
|
502
|
-
//# sourceMappingURL=chunk-
|
|
503
|
-
//# sourceMappingURL=chunk-
|
|
497
|
+
//# sourceMappingURL=chunk-CG7NXMBC.js.map
|
|
498
|
+
//# sourceMappingURL=chunk-CG7NXMBC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ui/chat-message.tsx"],"names":["_a","waveform"],"mappings":"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","file":"chunk-CG7NXMBC.js","sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Play, Pause, User } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Avatar, AvatarImage, AvatarFallback } from \"@/components/ui/avatar\"\nimport { LikeDislike, type LikeDislikeValue } from \"@/components/ui/like-dislike\"\nimport { Ai, CycleIcon } from \"@/components/icons\"\n\n/* ─── Container variants ──────────────────────────────────────── */\n\nconst chatMessageVariants = cva(\"flex w-full text-sm\", {\n variants: {\n persona: {\n ai: \"justify-start\",\n user: \"justify-end\",\n system: \"justify-center\",\n },\n },\n defaultVariants: {\n persona: \"ai\",\n },\n})\n\n/* ─── Types ───────────────────────────────────────────────────── */\n\nexport interface ChatMessageProps\n extends Omit<React.ComponentProps<\"div\">, \"content\">,\n VariantProps<typeof chatMessageVariants> {\n /** Quem enviou: AI (assistente), user (aluno), system (mensagem do sistema) */\n persona: \"ai\" | \"user\" | \"system\"\n /** Texto da mensagem. Convencao do produto:\n * - Em `persona=\"ai\"` com `audioSrc`, o texto e o roteiro/legenda do audio TTS.\n * - Em `persona=\"user\"` com `audioSrc`, o texto e a transcricao do audio (gerada por IA).\n * Em ambos os casos, aparece em bubble separada acima do audio. */\n text?: string\n /** URL do audio — renderiza AudioPlayer dentro do bubble. Convencao do produto:\n * toda msg da IA tem `text` + `audioSrc` (TTS) e toda msg do user com audio tem\n * `text` (transcricao). Garante acessibilidade (leitura + escuta) e busca no historico. */\n audioSrc?: string\n /** Avatar do user (so usado em persona=\"user\") */\n avatar?: string\n /** Nome do user para fallback do avatar */\n author?: string\n /** Mostrar like/dislike inline (so faz sentido em persona=\"ai\") */\n showFeedback?: boolean\n /** Valor controlado do feedback */\n feedbackValue?: LikeDislikeValue\n /** Callback quando feedback muda */\n onFeedbackChange?: (value: LikeDislikeValue) => void\n /** Estado de loading: mostra typing indicator (\"...\") */\n loading?: boolean\n}\n\n/* ─── Helpers ─────────────────────────────────────────────────── */\n\nfunction getInitials(name?: string): string {\n if (!name) return \"\"\n return name\n .split(\" \")\n .slice(0, 2)\n .map((w) => w[0])\n .join(\"\")\n .toUpperCase()\n}\n\n/* ─── Typing indicator ────────────────────────────────────────── */\n\nfunction TypingDots() {\n return (\n <span\n className=\"inline-flex items-center gap-1\"\n aria-label=\"Digitando...\"\n role=\"status\"\n >\n <span className=\"size-1.5 rounded-full bg-current opacity-40 animate-bounce [animation-delay:-0.3s]\" />\n <span className=\"size-1.5 rounded-full bg-current opacity-40 animate-bounce [animation-delay:-0.15s]\" />\n <span className=\"size-1.5 rounded-full bg-current opacity-40 animate-bounce\" />\n </span>\n )\n}\n\n/* ─── ChatAudio (compact inline audio player com waveform real) ── */\n\n/** Fallback estatico (valores 0-1) — usado enquanto peaks reais carregam ou quando audio nao pode ser analisado (CORS/decode error) */\nconst STATIC_FALLBACK_PEAKS = [\n 2, 2, 2, 2, 8, 24, 18, 24, 8, 18, 24, 24, 18, 24, 18, 18, 24, 24, 32, 24,\n 24, 32, 24, 32, 32, 24, 18, 24, 24, 18, 24, 18, 18, 24, 24, 24, 24, 24, 32, 24,\n 32, 18, 24, 18, 24, 18, 24, 18, 18, 24, 24, 24, 24, 24, 32, 24, 32, 18, 24, 18,\n 24, 8, 18, 24, 8, 4, 2, 2, 2, 2, 4, 8, 24, 24, 18, 24, 18, 18, 24, 24,\n 32, 24, 24, 32, 24, 32, 32, 24, 18, 24, 24, 18, 24, 8, 18, 24, 8, 2, 2, 4,\n].map((h) => h / 32)\n\nconst MIN_BAR_HEIGHT = 2 // px (silencio)\nconst MAX_BAR_HEIGHT = 32 // px (pico maximo)\nconst SPEED_OPTIONS = [1, 1.25, 1.5, 2, 0.75]\n\n/** Cache modular para evitar re-computar peaks do mesmo audio em multiplas instancias */\nconst peaksCache = new Map<string, number[]>()\n\n/**\n * Calcula picos (amplitude maxima) de um audio dividido em N buckets.\n * Usa Web Audio API (decodeAudioData) — sem dependencias externas.\n * Retorna array de valores 0-1 (normalizado pelo pico maximo).\n */\nasync function calculatePeaks(audioUrl: string, barCount: number): Promise<number[]> {\n const response = await fetch(audioUrl)\n const arrayBuffer = await response.arrayBuffer()\n\n const AudioContextClass =\n window.AudioContext ||\n (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext\n const audioContext = new AudioContextClass()\n const audioBuffer = await audioContext.decodeAudioData(arrayBuffer)\n\n const channelData = audioBuffer.getChannelData(0) // mono ou canal esquerdo\n const samplesPerBar = Math.floor(channelData.length / barCount)\n const peaks: number[] = []\n\n for (let i = 0; i < barCount; i++) {\n const start = i * samplesPerBar\n const end = Math.min(start + samplesPerBar, channelData.length)\n let max = 0\n for (let j = start; j < end; j++) {\n const absSample = Math.abs(channelData[j])\n if (absSample > max) max = absSample\n }\n peaks.push(max)\n }\n\n // Normaliza para 0-1 relativo ao pico maximo do clip\n const maxPeak = Math.max(...peaks, 0.01)\n\n // Fecha o AudioContext (libera recursos)\n void audioContext.close()\n\n return peaks.map((p) => p / maxPeak)\n}\n\n/** Wrapper com cache */\nasync function getPeaks(url: string, barCount: number): Promise<number[]> {\n const cacheKey = `${url}|${barCount}`\n const cached = peaksCache.get(cacheKey)\n if (cached) return cached\n const peaks = await calculatePeaks(url, barCount)\n peaksCache.set(cacheKey, peaks)\n return peaks\n}\n\nfunction formatAudioTime(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return \"00:00\"\n const m = Math.floor(seconds / 60)\n const s = Math.floor(seconds % 60)\n return `${m.toString().padStart(2, \"0\")}:${s.toString().padStart(2, \"0\")}`\n}\n\nexport interface ChatAudioProps extends React.ComponentProps<\"div\"> {\n /** URL do audio */\n src: string\n /**\n * Variante visual:\n * - `neutral`: container `bg-muted` (cinza claro) — usado em AI messages\n * - `brand`: container `bg-accent` (rosa pastel) — usado em User messages\n * (requer wrapper `.theme-brand` no componente pai)\n */\n variant?: \"neutral\" | \"brand\"\n /**\n * Array de picos pre-calculados (valores 0-1). Quando fornecido, o componente\n * usa direto sem analisar o audio. Util para SSR, performance ou audios sem CORS.\n * Se nao fornecido, o componente analisa o audio automaticamente via Web Audio API.\n */\n peaks?: number[]\n /**\n * Numero de barras no waveform (default: 60).\n * Ignorado se `peaks` for fornecido (usa o length do array).\n * Reduzido de 100 → 60 para barras nao ficarem finas demais em containers menores.\n */\n barCount?: number\n}\n\nfunction ChatAudio({\n src,\n variant = \"neutral\",\n peaks: providedPeaks,\n barCount = 60,\n className,\n ...props\n}: ChatAudioProps) {\n const audioRef = React.useRef<HTMLAudioElement>(null)\n const [isPlaying, setIsPlaying] = React.useState(false)\n const [currentTime, setCurrentTime] = React.useState(0)\n const [duration, setDuration] = React.useState(0)\n const [speed, setSpeed] = React.useState(1)\n const [computedPeaks, setComputedPeaks] = React.useState<number[] | null>(null)\n\n /** Auto-computa peaks se nao foram fornecidos */\n React.useEffect(() => {\n if (providedPeaks) return // consumer ja forneceu, skip analise\n let cancelled = false\n getPeaks(src, barCount)\n .then((p) => {\n if (!cancelled) setComputedPeaks(p)\n })\n .catch((err) => {\n /* fallback silencioso: mantem null, usa fallback estatico.\n Causa comum: CORS (audio source nao tem Access-Control-Allow-Origin).\n Log apenas em dev para nao poluir prod. */\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\n `[ChatAudio] Failed to analyze waveform for \"${src}\". ` +\n `Falling back to static waveform. ` +\n `Reason: ${err instanceof Error ? err.message : String(err)}. ` +\n `Audio source likely missing CORS headers (Access-Control-Allow-Origin).`\n )\n }\n })\n return () => {\n cancelled = true\n }\n }, [src, providedPeaks, barCount])\n\n /** Fallback estatico ajustado para o barCount atual (evita troca visual brusca quando peaks reais carregam) */\n const fallbackPeaks = React.useMemo(() => {\n return Array.from(\n { length: barCount },\n (_, i) => STATIC_FALLBACK_PEAKS[i % STATIC_FALLBACK_PEAKS.length]\n )\n }, [barCount])\n\n /** Resolve peaks: provided > computed > fallback (ajustado por barCount) */\n const peaks = providedPeaks ?? computedPeaks ?? fallbackPeaks\n\n /** Seta duracao apenas se valor for finito e positivo (evita Infinity/NaN) */\n const setDurationSafe = React.useCallback((value: number) => {\n if (Number.isFinite(value) && value > 0) {\n setDuration((prev) => (prev !== value ? value : prev))\n }\n }, [])\n\n /** Subscribe a multiplos eventos para garantir captura da duracao\n * (alguns audio sources nao disparam loadedmetadata, so durationchange/canplay) */\n React.useEffect(() => {\n const audio = audioRef.current\n if (!audio) return\n\n const handleDuration = () => setDurationSafe(audio.duration)\n handleDuration() // checa se ja esta carregado\n\n audio.addEventListener(\"loadedmetadata\", handleDuration)\n audio.addEventListener(\"durationchange\", handleDuration)\n audio.addEventListener(\"canplay\", handleDuration)\n return () => {\n audio.removeEventListener(\"loadedmetadata\", handleDuration)\n audio.removeEventListener(\"durationchange\", handleDuration)\n audio.removeEventListener(\"canplay\", handleDuration)\n }\n }, [src, setDurationSafe])\n\n const togglePlay = () => {\n const audio = audioRef.current\n if (!audio) return\n if (audio.paused) {\n audio.play().catch(() => {})\n } else {\n audio.pause()\n }\n }\n\n const cycleSpeed = () => {\n const idx = SPEED_OPTIONS.indexOf(speed)\n const next = SPEED_OPTIONS[(idx + 1) % SPEED_OPTIONS.length] ?? 1\n setSpeed(next)\n if (audioRef.current) audioRef.current.playbackRate = next\n }\n\n /* ─── Seek/drag via pointer no waveform ───────────────────── */\n\n const waveformRef = React.useRef<HTMLDivElement>(null)\n const isDraggingRef = React.useRef(false)\n\n /** Converte clientX (event) em time do audio, baseado na largura do container do waveform */\n const seekToClientX = React.useCallback(\n (clientX: number) => {\n const audio = audioRef.current\n const waveform = waveformRef.current\n if (!audio || !waveform || !duration) return\n const rect = waveform.getBoundingClientRect()\n const x = clientX - rect.left\n const newProgress = Math.max(0, Math.min(x / rect.width, 1))\n audio.currentTime = newProgress * duration\n },\n [duration]\n )\n\n const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!duration) return\n isDraggingRef.current = true\n e.currentTarget.setPointerCapture(e.pointerId)\n seekToClientX(e.clientX)\n }\n\n const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!isDraggingRef.current) return\n seekToClientX(e.clientX)\n }\n\n const handlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!isDraggingRef.current) return\n isDraggingRef.current = false\n e.currentTarget.releasePointerCapture(e.pointerId)\n }\n\n const progress = duration > 0 ? currentTime / duration : 0\n const displayTime = formatAudioTime(currentTime)\n\n /* Waveform JSX — reusado entre layouts mobile (top) e desktop (inline).\n Barras com largura FIXA de 2px (matches Figma) distribuidas via justify-between\n para preencher toda a largura do container — gap entre barras se torna fluido\n conforme o waveform expande. Garante espacamento simetrico com time (esquerda)\n e speed badge (direita). Pointer events permitem clicar/arrastar para seek. */\n const waveform = (\n <div\n ref={waveformRef}\n className=\"flex-1 relative h-8 min-w-0 touch-none cursor-pointer select-none\"\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n role=\"slider\"\n aria-label=\"Posicao do audio\"\n aria-valuemin={0}\n aria-valuemax={Math.round(duration)}\n aria-valuenow={Math.round(currentTime)}\n >\n <div className=\"absolute inset-0 flex items-center justify-between\">\n {peaks.map((peak, i) => {\n const isPlayed = i / peaks.length <= progress\n const height = Math.max(\n MIN_BAR_HEIGHT,\n Math.round(peak * MAX_BAR_HEIGHT)\n )\n return (\n <div\n key={i}\n className={cn(\n \"w-[2px] shrink-0 rounded-full\",\n isPlayed ? \"bg-neutral-foreground\" : \"bg-neutral-ring\"\n )}\n style={{ height: `${height}px` }}\n />\n )\n })}\n </div>\n {/* Bolinha verde — posicionada em PORCENTAGEM da largura do container,\n alinhada com a distribuicao fluida das barras. */}\n {duration > 0 && (\n <div\n className=\"absolute top-1/2 z-10 -translate-y-1/2 -translate-x-1/2 size-3 rounded-full bg-[#098A5E] pointer-events-none shadow-md\"\n style={{ left: `${progress * 100}%` }}\n aria-hidden=\"true\"\n />\n )}\n </div>\n )\n\n /* Time display — reusado entre layouts */\n const timeDisplay = (\n <span className=\"shrink-0 text-sm text-neutral-ring tabular-nums\">\n {displayTime}\n </span>\n )\n\n /* Speed badge — reusado entre layouts */\n const speedBadge = (\n <button\n type=\"button\"\n onClick={cycleSpeed}\n className={cn(\n \"shrink-0 h-[26px] min-w-16 rounded-full text-sm font-medium px-2 transition-opacity hover:opacity-80\",\n variant === \"brand\"\n ? \"bg-muted text-muted-foreground\"\n : \"bg-background text-neutral-muted-foreground\"\n )}\n aria-label={`Velocidade ${speed}x. Clique para alternar.`}\n >\n {speed === 1 ? \"1x\" : `${speed}x`}\n </button>\n )\n\n return (\n <div\n data-slot=\"chat-audio\"\n className={cn(\n \"rounded-2xl w-full max-w-md flex flex-col gap-2 p-3 sm:flex-row sm:items-center sm:gap-2 sm:pl-5 sm:pr-3 sm:py-3\",\n variant === \"brand\" ? \"bg-accent\" : \"bg-muted\",\n className\n )}\n {...props}\n >\n <audio\n ref={audioRef}\n src={src}\n preload=\"metadata\"\n onPlay={() => setIsPlaying(true)}\n onPause={() => setIsPlaying(false)}\n onTimeUpdate={(e) => {\n setCurrentTime(e.currentTarget.currentTime)\n /* Fallback: alguns sources so expoem duration durante o playback */\n setDurationSafe(e.currentTarget.duration)\n }}\n onEnded={() => setIsPlaying(false)}\n />\n\n {/* Mobile top row + Desktop inline row */}\n <div className=\"flex items-center gap-2 flex-1 min-w-0\">\n {/* Play/Pause — icones FILLED */}\n <button\n type=\"button\"\n onClick={togglePlay}\n className=\"shrink-0 flex items-center justify-center text-neutral-foreground hover:opacity-80 transition-opacity\"\n aria-label={isPlaying ? \"Pausar\" : \"Reproduzir\"}\n >\n {isPlaying ? (\n <Pause className=\"size-4\" fill=\"currentColor\" strokeWidth={0} />\n ) : (\n <Play className=\"size-4\" fill=\"currentColor\" strokeWidth={0} />\n )}\n </button>\n\n {/* Time — apenas desktop (no mobile aparece embaixo) */}\n <div className=\"hidden sm:contents\">{timeDisplay}</div>\n\n {waveform}\n\n {/* Speed — apenas desktop (no mobile aparece embaixo) */}\n <div className=\"hidden sm:contents\">{speedBadge}</div>\n </div>\n\n {/* Mobile bottom row: time + speed badge */}\n <div className=\"flex items-center justify-between sm:hidden\">\n {timeDisplay}\n {speedBadge}\n </div>\n </div>\n )\n}\n\nexport { ChatAudio }\n\n/* ─── Component ───────────────────────────────────────────────── */\n\nfunction ChatMessage({\n persona,\n text,\n audioSrc,\n avatar,\n author,\n showFeedback = false,\n feedbackValue,\n onFeedbackChange,\n loading = false,\n className,\n ...props\n}: ChatMessageProps) {\n /* System: centered italic */\n if (persona === \"system\") {\n return (\n <div\n data-slot=\"chat-message\"\n data-persona=\"system\"\n className={cn(chatMessageVariants({ persona }), className)}\n {...props}\n >\n <span className=\"text-xs text-muted-foreground italic\">{text}</span>\n </div>\n )\n }\n\n /* AI: badge quadrado (esquerda, apenas desktop) + conteudo empilhado */\n if (persona === \"ai\") {\n return (\n <div\n data-slot=\"chat-message\"\n data-persona=\"ai\"\n className={cn(chatMessageVariants({ persona }), className)}\n {...props}\n >\n <div className=\"flex gap-3 max-w-[85%] sm:max-w-[75%] w-full\">\n {/* AI badge: oculto no mobile, visivel no desktop */}\n <div className=\"hidden sm:flex shrink-0 size-8 rounded-md bg-primary items-center justify-center theme-brand\">\n <span className=\"text-primary-foreground\">\n <Ai size=\"sm\" decorative />\n </span>\n </div>\n <div className=\"flex flex-col gap-2 min-w-0 flex-1\">\n {loading ? (\n /* Loading: dots dentro de bubble cinza (speech bubble shape — top-left reto em todos os breakpoints) */\n <div className=\"self-start inline-flex items-center rounded-2xl rounded-tl-none bg-muted p-5 text-muted-foreground\">\n <TypingDots />\n </div>\n ) : (\n <>\n {text && (\n /* Text bubble: bg-muted sempre, speech bubble shape (tl-none) em todos os breakpoints */\n <div className=\"rounded-2xl rounded-tl-none bg-muted p-5\">\n <p className=\"text-base leading-6 text-neutral-foreground break-words\">{text}</p>\n </div>\n )}\n {audioSrc && <ChatAudio src={audioSrc} variant=\"neutral\" />}\n {showFeedback && (\n <LikeDislike\n size=\"xs\"\n value={feedbackValue}\n onValueChange={onFeedbackChange}\n />\n )}\n </>\n )}\n </div>\n </div>\n </div>\n )\n }\n\n /* User: bubble (direita) + avatar (apenas desktop, alinhado ao topo) */\n return (\n <div\n data-slot=\"chat-message\"\n data-persona=\"user\"\n className={cn(chatMessageVariants({ persona }), className)}\n {...props}\n >\n <div className=\"flex items-start gap-3 max-w-[85%] sm:max-w-[75%] w-full justify-end\">\n <div className=\"flex flex-col gap-2 min-w-0 items-end theme-brand flex-1\">\n {loading ? (\n /* Loading: dots dentro de bubble pink (speech bubble shape — top-right reto em todos os breakpoints) */\n <div className=\"inline-flex items-center rounded-2xl rounded-tr-none bg-accent p-5 text-neutral-muted-foreground\">\n <TypingDots />\n </div>\n ) : (\n <>\n {text && (\n /* Text bubble: speech bubble shape (top-right reto) em todos os breakpoints.\n text-neutral-foreground = preto neutro fixo, nao muda com theme-brand */\n <div className=\"rounded-2xl rounded-tr-none bg-accent p-5\">\n <p className=\"text-base leading-6 text-neutral-foreground break-words\">{text}</p>\n </div>\n )}\n {audioSrc && <ChatAudio src={audioSrc} variant=\"brand\" />}\n </>\n )}\n </div>\n {/* Avatar: 40x40 rounded-lg, oculto no mobile, alinhado ao topo */}\n <Avatar size=\"lg\" className=\"hidden sm:flex shrink-0 rounded-lg\">\n {avatar && (\n <AvatarImage src={avatar} alt={author ?? \"User\"} className=\"rounded-lg\" />\n )}\n <AvatarFallback className=\"rounded-lg bg-muted text-muted-foreground text-sm\">\n {author ? (\n getInitials(author)\n ) : (\n <CycleIcon icon={User} size=\"sm\" decorative />\n )}\n </AvatarFallback>\n </Avatar>\n </div>\n </div>\n )\n}\n\nexport { ChatMessage, chatMessageVariants }\n"]}
|
|
@@ -10,6 +10,13 @@ function formatDuration(seconds) {
|
|
|
10
10
|
const s = Math.floor(seconds % 60);
|
|
11
11
|
return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
|
|
12
12
|
}
|
|
13
|
+
function getWarningState(elapsed, max, warnAt) {
|
|
14
|
+
if (!max || max <= 0) return { isWarning: false, secondsLeft: Infinity };
|
|
15
|
+
const secondsLeft = Math.max(0, max - elapsed);
|
|
16
|
+
return { isWarning: secondsLeft <= warnAt, secondsLeft };
|
|
17
|
+
}
|
|
18
|
+
var DEFAULT_WARN_AT_SECONDS_LEFT = 10;
|
|
19
|
+
var DEFAULT_SECONDS_LEFT_LABEL = (s) => `${s}s restantes`;
|
|
13
20
|
function MicButton({
|
|
14
21
|
onClick,
|
|
15
22
|
onPointerDown,
|
|
@@ -337,7 +344,10 @@ function PressedRecordingOverlay({
|
|
|
337
344
|
deltaX,
|
|
338
345
|
deltaY,
|
|
339
346
|
lockThreshold,
|
|
340
|
-
cancelThreshold
|
|
347
|
+
cancelThreshold,
|
|
348
|
+
isWarning,
|
|
349
|
+
secondsLeft,
|
|
350
|
+
secondsLeftLabel
|
|
341
351
|
}) {
|
|
342
352
|
const aboutToLock = deltaY <= -lockThreshold;
|
|
343
353
|
const aboutToCancel = deltaX <= -cancelThreshold;
|
|
@@ -405,9 +415,28 @@ function PressedRecordingOverlay({
|
|
|
405
415
|
}
|
|
406
416
|
)
|
|
407
417
|
] }),
|
|
408
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-
|
|
409
|
-
/* @__PURE__ */
|
|
410
|
-
|
|
418
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
|
|
419
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-full bg-muted h-7 px-3", children: [
|
|
420
|
+
/* @__PURE__ */ jsx(RecordingIndicator, {}),
|
|
421
|
+
/* @__PURE__ */ jsx(
|
|
422
|
+
"span",
|
|
423
|
+
{
|
|
424
|
+
className: cn(
|
|
425
|
+
"text-sm tabular-nums",
|
|
426
|
+
isWarning ? "text-destructive font-medium" : "text-neutral-foreground"
|
|
427
|
+
),
|
|
428
|
+
children: formatDuration(duration)
|
|
429
|
+
}
|
|
430
|
+
)
|
|
431
|
+
] }),
|
|
432
|
+
isWarning && /* @__PURE__ */ jsx(
|
|
433
|
+
"span",
|
|
434
|
+
{
|
|
435
|
+
className: "text-xs text-destructive tabular-nums",
|
|
436
|
+
"aria-live": "polite",
|
|
437
|
+
children: secondsLeftLabel(secondsLeft)
|
|
438
|
+
}
|
|
439
|
+
)
|
|
411
440
|
] })
|
|
412
441
|
] }) : (
|
|
413
442
|
// Variant default: cancel pill flex-1 + mic
|
|
@@ -420,9 +449,28 @@ function PressedRecordingOverlay({
|
|
|
420
449
|
aboutToCancel && "bg-destructive/10"
|
|
421
450
|
),
|
|
422
451
|
children: [
|
|
423
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-
|
|
424
|
-
/* @__PURE__ */
|
|
425
|
-
|
|
452
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-0.5", children: [
|
|
453
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
454
|
+
/* @__PURE__ */ jsx(RecordingIndicator, {}),
|
|
455
|
+
/* @__PURE__ */ jsx(
|
|
456
|
+
"span",
|
|
457
|
+
{
|
|
458
|
+
className: cn(
|
|
459
|
+
"text-sm tabular-nums",
|
|
460
|
+
isWarning ? "text-destructive font-medium" : "text-neutral-foreground"
|
|
461
|
+
),
|
|
462
|
+
children: formatDuration(duration)
|
|
463
|
+
}
|
|
464
|
+
)
|
|
465
|
+
] }),
|
|
466
|
+
isWarning && /* @__PURE__ */ jsx(
|
|
467
|
+
"span",
|
|
468
|
+
{
|
|
469
|
+
className: "text-xs text-destructive tabular-nums",
|
|
470
|
+
"aria-live": "polite",
|
|
471
|
+
children: secondsLeftLabel(secondsLeft)
|
|
472
|
+
}
|
|
473
|
+
)
|
|
426
474
|
] }),
|
|
427
475
|
/* @__PURE__ */ jsxs(
|
|
428
476
|
"div",
|
|
@@ -489,6 +537,10 @@ function MessageBar(_a) {
|
|
|
489
537
|
playbackProgress = 0,
|
|
490
538
|
onSeekPlayback,
|
|
491
539
|
placeholder = "Digite sua mensagem...",
|
|
540
|
+
maxRecordingDuration,
|
|
541
|
+
warnAtSecondsLeft = DEFAULT_WARN_AT_SECONDS_LEFT,
|
|
542
|
+
onMaxDurationReached,
|
|
543
|
+
secondsLeftLabel = DEFAULT_SECONDS_LEFT_LABEL,
|
|
492
544
|
className
|
|
493
545
|
} = _b, props = __objRest(_b, [
|
|
494
546
|
"state",
|
|
@@ -507,8 +559,24 @@ function MessageBar(_a) {
|
|
|
507
559
|
"playbackProgress",
|
|
508
560
|
"onSeekPlayback",
|
|
509
561
|
"placeholder",
|
|
562
|
+
"maxRecordingDuration",
|
|
563
|
+
"warnAtSecondsLeft",
|
|
564
|
+
"onMaxDurationReached",
|
|
565
|
+
"secondsLeftLabel",
|
|
510
566
|
"className"
|
|
511
567
|
]);
|
|
568
|
+
const { isWarning: isDurationWarning, secondsLeft: durationSecondsLeft } = getWarningState(recordingDuration, maxRecordingDuration, warnAtSecondsLeft);
|
|
569
|
+
const maxReachedRef = React.useRef(false);
|
|
570
|
+
React.useEffect(() => {
|
|
571
|
+
if (!maxRecordingDuration || maxReachedRef.current) return;
|
|
572
|
+
if (recordingDuration >= maxRecordingDuration) {
|
|
573
|
+
maxReachedRef.current = true;
|
|
574
|
+
onMaxDurationReached == null ? void 0 : onMaxDurationReached();
|
|
575
|
+
}
|
|
576
|
+
}, [recordingDuration, maxRecordingDuration, onMaxDurationReached]);
|
|
577
|
+
React.useEffect(() => {
|
|
578
|
+
if (state !== "recording") maxReachedRef.current = false;
|
|
579
|
+
}, [state]);
|
|
512
580
|
const samplesRef = React.useRef([]);
|
|
513
581
|
const prevStateRef = React.useRef(state);
|
|
514
582
|
const lastBaseStateRef = React.useRef(
|
|
@@ -654,7 +722,10 @@ function MessageBar(_a) {
|
|
|
654
722
|
deltaX: pressDelta.x,
|
|
655
723
|
deltaY: pressDelta.y,
|
|
656
724
|
lockThreshold: LOCK_THRESHOLD,
|
|
657
|
-
cancelThreshold: pressCancelThreshold
|
|
725
|
+
cancelThreshold: pressCancelThreshold,
|
|
726
|
+
isWarning: isDurationWarning,
|
|
727
|
+
secondsLeft: durationSecondsLeft,
|
|
728
|
+
secondsLeftLabel
|
|
658
729
|
}
|
|
659
730
|
);
|
|
660
731
|
}
|
|
@@ -681,7 +752,26 @@ function MessageBar(_a) {
|
|
|
681
752
|
) }),
|
|
682
753
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-1 rounded-2xl bg-muted px-4 py-3", children: [
|
|
683
754
|
/* @__PURE__ */ jsx(RecordingIndicator, {}),
|
|
684
|
-
/* @__PURE__ */
|
|
755
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-0.5 shrink-0", children: [
|
|
756
|
+
/* @__PURE__ */ jsx(
|
|
757
|
+
"span",
|
|
758
|
+
{
|
|
759
|
+
className: cn(
|
|
760
|
+
"text-sm tabular-nums",
|
|
761
|
+
isDurationWarning ? "text-destructive font-medium" : "text-neutral-foreground"
|
|
762
|
+
),
|
|
763
|
+
children: formatDuration(recordingDuration)
|
|
764
|
+
}
|
|
765
|
+
),
|
|
766
|
+
isDurationWarning && /* @__PURE__ */ jsx(
|
|
767
|
+
"span",
|
|
768
|
+
{
|
|
769
|
+
className: "text-xs text-destructive tabular-nums whitespace-nowrap",
|
|
770
|
+
"aria-live": "polite",
|
|
771
|
+
children: secondsLeftLabel(durationSecondsLeft)
|
|
772
|
+
}
|
|
773
|
+
)
|
|
774
|
+
] }),
|
|
685
775
|
/* @__PURE__ */ jsx(LiveWaveform, { stream: recordingStream, samplesRef }),
|
|
686
776
|
/* @__PURE__ */ jsx("div", { className: "hidden sm:contents", children: /* @__PURE__ */ jsx(
|
|
687
777
|
"button",
|
|
@@ -842,5 +932,5 @@ function MessageBar(_a) {
|
|
|
842
932
|
}
|
|
843
933
|
|
|
844
934
|
export { MessageBar };
|
|
845
|
-
//# sourceMappingURL=chunk-
|
|
846
|
-
//# sourceMappingURL=chunk-
|
|
935
|
+
//# sourceMappingURL=chunk-CWMXYPWK.js.map
|
|
936
|
+
//# sourceMappingURL=chunk-CWMXYPWK.js.map
|