@fluencypassdevs/cycle 1.27.2 → 1.29.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.
@@ -1,13 +1,16 @@
1
1
  import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from './chunk-2KFWBA76.js';
2
- import { ChatMessage } from './chunk-VUVC2NAK.js';
2
+ import { ChatMessage } from './chunk-KSEDDFGG.js';
3
3
  import { DEFAULT_MESSAGE_RATING_LABELS, MessageRating } from './chunk-YIOXPV2N.js';
4
- import { MessageBar } from './chunk-TK67WF35.js';
4
+ import { MessageBar } from './chunk-5AH2PNYW.js';
5
5
  import { cn } from './chunk-W4KJCBMW.js';
6
6
  import { __objRest, __spreadValues, __spreadProps } from './chunk-2NMEKWO5.js';
7
7
  import * as React from 'react';
8
- import { ChevronDown, Square } from 'lucide-react';
8
+ import { Square } from 'lucide-react';
9
9
  import { jsxs, jsx } from 'react/jsx-runtime';
10
10
 
11
+ function isThenable(value) {
12
+ return !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
13
+ }
11
14
  var SCROLL_BOTTOM_THRESHOLD = 100;
12
15
  var LOAD_MORE_THRESHOLD = 200;
13
16
  function ChatThreadBanner({
@@ -57,30 +60,6 @@ function StopResponseButton({
57
60
  }
58
61
  );
59
62
  }
60
- function ScrollToBottomButton({
61
- onClick,
62
- hasNewMessage
63
- }) {
64
- return /* @__PURE__ */ jsxs(
65
- "button",
66
- {
67
- type: "button",
68
- onClick,
69
- className: "absolute bottom-2 left-1/2 -translate-x-1/2 z-10 inline-flex items-center justify-center size-10 rounded-full bg-background border border-border shadow-md hover:bg-muted/50 transition-all focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
70
- "aria-label": hasNewMessage ? "Nova mensagem \u2014 ir para o fim" : "Ir para o fim",
71
- children: [
72
- /* @__PURE__ */ jsx(ChevronDown, { className: "size-5 text-neutral-foreground" }),
73
- hasNewMessage && /* @__PURE__ */ jsx(
74
- "span",
75
- {
76
- className: "absolute top-1 right-1 size-2 rounded-full bg-primary theme-brand",
77
- "aria-hidden": "true"
78
- }
79
- )
80
- ]
81
- }
82
- );
83
- }
84
63
  function MessageBubbleAnimated({
85
64
  children,
86
65
  enableAnimation
@@ -136,6 +115,7 @@ function ChatThread(_a) {
136
115
  onStartRecording,
137
116
  onPauseRecording,
138
117
  onResumeRecording,
118
+ onRecordingError,
139
119
  onCancelRecording,
140
120
  onTogglePlay,
141
121
  onSeekPlayback,
@@ -159,6 +139,7 @@ function ChatThread(_a) {
159
139
  onMaxDurationReached,
160
140
  userAvatar,
161
141
  userName,
142
+ userMessageTheme,
162
143
  offlineBannerText = "Sem conexao. Tentando reconectar...",
163
144
  retryButtonText = "Tentar novamente",
164
145
  regenerateButtonText = "Regenerar resposta",
@@ -178,6 +159,7 @@ function ChatThread(_a) {
178
159
  "onStartRecording",
179
160
  "onPauseRecording",
180
161
  "onResumeRecording",
162
+ "onRecordingError",
181
163
  "onCancelRecording",
182
164
  "onTogglePlay",
183
165
  "onSeekPlayback",
@@ -201,6 +183,7 @@ function ChatThread(_a) {
201
183
  "onMaxDurationReached",
202
184
  "userAvatar",
203
185
  "userName",
186
+ "userMessageTheme",
204
187
  "offlineBannerText",
205
188
  "retryButtonText",
206
189
  "regenerateButtonText",
@@ -214,7 +197,6 @@ function ChatThread(_a) {
214
197
  const isDisabled = offline || isRateLimited || quotaExhausted;
215
198
  const scrollRef = React.useRef(null);
216
199
  const [isAtBottom, setIsAtBottom] = React.useState(true);
217
- const [hasNewBelow, setHasNewBelow] = React.useState(false);
218
200
  const prevMessageCountRef = React.useRef(messages.length);
219
201
  const loadMoreInFlightRef = React.useRef(false);
220
202
  const [isLoadingMore, setIsLoadingMore] = React.useState(false);
@@ -228,7 +210,6 @@ function ChatThread(_a) {
228
210
  const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
229
211
  const atBottom = distanceFromBottom <= SCROLL_BOTTOM_THRESHOLD;
230
212
  setIsAtBottom(atBottom);
231
- if (atBottom) setHasNewBelow(false);
232
213
  }, []);
233
214
  const scrollToBottom = React.useCallback((smooth = true) => {
234
215
  const el = scrollRef.current;
@@ -237,7 +218,6 @@ function ChatThread(_a) {
237
218
  top: el.scrollHeight,
238
219
  behavior: smooth ? "smooth" : "auto"
239
220
  });
240
- setHasNewBelow(false);
241
221
  }, []);
242
222
  React.useEffect(() => {
243
223
  const prev = prevMessageCountRef.current;
@@ -246,8 +226,6 @@ function ChatThread(_a) {
246
226
  if (curr <= prev) return;
247
227
  if (isAtBottom) {
248
228
  requestAnimationFrame(() => scrollToBottom(true));
249
- } else {
250
- setHasNewBelow(true);
251
229
  }
252
230
  }, [messages.length]);
253
231
  React.useEffect(() => {
@@ -298,27 +276,59 @@ function ChatThread(_a) {
298
276
  );
299
277
  const baseMessageBarState = audioOnlyMode ? "audio-only" : "default";
300
278
  const [internalRecordingState, setInternalRecordingState] = React.useState("idle");
279
+ const recordingRequestTokenRef = React.useRef(0);
280
+ const isMountedRef = React.useRef(false);
281
+ React.useEffect(() => {
282
+ isMountedRef.current = true;
283
+ return () => {
284
+ isMountedRef.current = false;
285
+ recordingRequestTokenRef.current++;
286
+ };
287
+ }, []);
301
288
  React.useEffect(() => {
302
289
  if (internalRecordingState === "idle") return;
303
290
  }, [audioOnlyMode, internalRecordingState]);
304
- const messageBarState = isDisabled ? "disabled" : internalRecordingState === "recording" ? "recording" : internalRecordingState === "paused" ? "paused" : baseMessageBarState;
291
+ const messageBarState = isDisabled ? "disabled" : internalRecordingState === "recording" ? "recording" : internalRecordingState === "paused" ? "paused" : internalRecordingState === "requesting" ? "requesting" : baseMessageBarState;
292
+ const runRecordingRequest = React.useCallback(
293
+ (callback, fallbackState) => {
294
+ const result = callback == null ? void 0 : callback();
295
+ if (!isThenable(result)) {
296
+ setInternalRecordingState("recording");
297
+ return;
298
+ }
299
+ const token = ++recordingRequestTokenRef.current;
300
+ setInternalRecordingState("requesting");
301
+ result.then(
302
+ () => {
303
+ if (!isMountedRef.current || recordingRequestTokenRef.current !== token) return;
304
+ setInternalRecordingState("recording");
305
+ },
306
+ (error) => {
307
+ if (!isMountedRef.current || recordingRequestTokenRef.current !== token) return;
308
+ setInternalRecordingState(fallbackState);
309
+ onRecordingError == null ? void 0 : onRecordingError(error);
310
+ }
311
+ );
312
+ },
313
+ [onRecordingError]
314
+ );
305
315
  const handleStartRecordingInternal = React.useCallback(() => {
306
- setInternalRecordingState("recording");
307
- onStartRecording == null ? void 0 : onStartRecording();
308
- }, [onStartRecording]);
316
+ runRecordingRequest(onStartRecording, "idle");
317
+ }, [onStartRecording, runRecordingRequest]);
309
318
  const handlePauseRecordingInternal = React.useCallback(() => {
310
319
  setInternalRecordingState("paused");
311
320
  onPauseRecording == null ? void 0 : onPauseRecording();
312
321
  }, [onPauseRecording]);
313
322
  const handleResumeRecordingInternal = React.useCallback(() => {
314
- setInternalRecordingState("recording");
315
- onResumeRecording == null ? void 0 : onResumeRecording();
316
- }, [onResumeRecording]);
323
+ runRecordingRequest(onResumeRecording, "paused");
324
+ }, [onResumeRecording, runRecordingRequest]);
317
325
  const handleCancelRecordingInternal = React.useCallback(() => {
326
+ recordingRequestTokenRef.current++;
318
327
  setInternalRecordingState("idle");
319
328
  onCancelRecording == null ? void 0 : onCancelRecording();
320
329
  }, [onCancelRecording]);
321
330
  const handleSendAudioInternal = React.useCallback(() => {
331
+ recordingRequestTokenRef.current++;
322
332
  setInternalRecordingState("idle");
323
333
  onSendAudio == null ? void 0 : onSendAudio();
324
334
  }, [onSendAudio]);
@@ -356,7 +366,7 @@ function ChatThread(_a) {
356
366
  "div",
357
367
  {
358
368
  ref: scrollRef,
359
- className: "flex-1 overflow-y-auto px-4 py-4 space-y-4",
369
+ className: "flex-1 overflow-y-auto px-4 py-4 space-y-4 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
360
370
  role: "log",
361
371
  "aria-live": "polite",
362
372
  "aria-atomic": "false",
@@ -376,6 +386,7 @@ function ChatThread(_a) {
376
386
  audioSrc: msg.audioSrc,
377
387
  avatar: msg.persona === "user" ? userAvatar : void 0,
378
388
  author: msg.persona === "user" ? userName : void 0,
389
+ theme: userMessageTheme,
379
390
  loading: isPendingUser && !msg.text && !msg.audioSrc,
380
391
  className: cn(
381
392
  isPendingUser && "opacity-80",
@@ -427,13 +438,6 @@ function ChatThread(_a) {
427
438
  ]
428
439
  }
429
440
  ),
430
- !isAtBottom && /* @__PURE__ */ jsx(
431
- ScrollToBottomButton,
432
- {
433
- onClick: () => scrollToBottom(true),
434
- hasNewMessage: hasNewBelow
435
- }
436
- ),
437
441
  /* @__PURE__ */ jsx(
438
442
  BottomSlot,
439
443
  {
@@ -482,5 +486,5 @@ function ChatThread(_a) {
482
486
  }
483
487
 
484
488
  export { ChatThread };
485
- //# sourceMappingURL=chunk-EAVNA2RW.js.map
486
- //# sourceMappingURL=chunk-EAVNA2RW.js.map
489
+ //# sourceMappingURL=chunk-MQX4NBKQ.js.map
490
+ //# sourceMappingURL=chunk-MQX4NBKQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/composites/chat-thread.tsx"],"names":[],"mappings":"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","file":"chunk-MQX4NBKQ.js","sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Square } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { ChatMessage } from \"@/components/ui/chat-message\"\nimport {\n MessageBar,\n type MessageBarState,\n} from \"@/components/ui/message-bar\"\nimport {\n MessageRating,\n DEFAULT_MESSAGE_RATING_LABELS,\n type MessageRatingValue,\n type MessageRatingLabels,\n type RatingLabel,\n} from \"@/components/ui/message-rating\"\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from \"@/components/ui/alert-dialog\"\n\n/** Duck-typing de Promise/thenable — aceita Promise nativa e qualquer objeto com .then */\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n !!value &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n typeof (value as { then?: unknown }).then === \"function\"\n )\n}\n\n/* ─── Types ───────────────────────────────────────────────────── */\n\nexport type ChatThreadMessageStatus = \"sent\" | \"pending\" | \"failed\"\nexport type ChatThreadPersona = \"ai\" | \"user\" | \"system\"\n\nexport interface ChatThreadMessage {\n id: string\n persona: ChatThreadPersona\n status?: ChatThreadMessageStatus\n text?: string\n audioSrc?: string\n audioPeaks?: number[]\n /** Mensagem de erro pra exibir quando `persona=\"ai\"` e `status=\"failed\"` */\n errorText?: string\n /**\n * Quando true em uma mensagem da IA, renderiza o `MessageRating` (5 emojis) logo\n * abaixo da bubble — alinhado com a IA, parte da mesma \"linha visual\" da mensagem.\n * Apos o usuario responder, o rating some automaticamente (fade-out + slide-down).\n *\n * Usado quando a IA pede avaliacao da atividade: o consumer adiciona a msg da IA\n * com este campo `true` no array `messages` e ouve `onRate` para receber a resposta.\n */\n requestRating?: boolean\n}\n\nexport type ChatThreadState = \"idle\" | \"sending\" | \"thinking\" | \"error\"\n\nexport interface QuotaExhaustedConfig {\n title?: string\n description?: string\n ctaLabel?: string\n cancelLabel?: string\n onCta?: () => void\n onCancel?: () => void\n}\n\nexport interface ChatThreadProps extends Omit<React.ComponentProps<\"div\">, \"onChange\"> {\n /** Lista de mensagens da conversa */\n messages: ChatThreadMessage[]\n /** Estado global da conversa (controlled) */\n state?: ChatThreadState\n /** Quando true e thread vazio, monta direto em thinking (cenario Class — IA fala primeiro) */\n initialThinking?: boolean\n\n /* ─── Callbacks essenciais ─── */\n onSendText?: (text: string) => void\n /** Disparado quando user confirma envio do audio gravado (MessageBar dispara apos release/lock).\n * Consumer gerencia MediaRecorder e tem acesso ao Blob no momento que isso for chamado. */\n onSendAudio?: () => void\n /** Retry de mensagem do usuario que falhou enviar (B2) */\n onRetryMessage?: (messageId: string) => void\n /** Regenerar resposta da IA com erro (B3) */\n onRegenerateResponse?: () => void\n /** Cancelar resposta da IA mid-thinking (F1). Passar este callback **ativa** o botao\n * \"Parar resposta\" no rodape durante o estado `thinking`. Se omitido, o MessageBar\n * some com animacao de saida quando a IA esta pensando e volta com animacao de entrada\n * quando termina — o consumer indica que nao quer expor cancelamento. */\n onStopResponse?: () => void\n /** Carregar mensagens antigas (G1 — scroll infinito) */\n onLoadMore?: () => Promise<void> | void\n\n /* ─── Callbacks do MessageBar (pass-through) ─── */\n /** Dispara quando o user pressiona o mic. Consumer chama getUserMedia + MediaRecorder.\n * Pode retornar uma Promise: enquanto ela estiver pendente o MessageBar fica em\n * `requesting` (mic com spinner) e so entra em `recording` quando resolver. Se\n * rejeitar, volta pro estado anterior e dispara `onRecordingError`. Retorno sincrono\n * (void) mantem o comportamento antigo: entra em `recording` imediatamente.\n * Recomendado: `onStartRecording={() => startRecording()}` retornando a Promise do\n * getUserMedia — evita mostrar \"gravando\" antes do microfone existir. */\n onStartRecording?: () => void | Promise<unknown>\n /** Dispara quando o user pausa a gravacao. Consumer faz recorder.stop() + cria Blob. */\n onPauseRecording?: () => void\n /** Dispara quando o user retoma gravacao apos pause. Mesma semantica de Promise do\n * `onStartRecording` (rejeitou → volta pra `paused`). */\n onResumeRecording?: () => void | Promise<unknown>\n /** Dispara quando a Promise de `onStartRecording`/`onResumeRecording` rejeita (ex.:\n * permissao de microfone negada). O ChatThread ja restaurou o estado do MessageBar;\n * o consumer decide o feedback (toast, alert, banner). */\n onRecordingError?: (error: unknown) => void\n /** Dispara quando o user cancela a gravacao. Consumer cleanup do MediaRecorder. */\n onCancelRecording?: () => void\n /** Toggle play/pause do audio gravado durante o estado `paused` do MessageBar. */\n onTogglePlay?: () => void\n /** Callback de seek no waveform do audio gravado (durante paused). */\n onSeekPlayback?: (progress: number) => void\n\n /* ─── State do MessageBar (pass-through) ─── */\n /** Stream do microfone durante recording — usado pelo MessageBar pra renderizar o live waveform. */\n recordingStream?: MediaStream | null\n /** Duracao da gravacao em segundos (controlado pelo consumer via setInterval). */\n recordingDuration?: number\n /** Indica se o audio gravado esta tocando (durante paused). */\n isPlaying?: boolean\n /** Progresso de playback (0-1) — usado pra sincronizar dot verde no waveform. */\n playbackProgress?: number\n\n /* ─── Erro states (configuravel) ─── */\n /** Mostra banner de offline + disabled MessageBar (C1) */\n offline?: boolean\n /** Timestamp epoch ate quando bloquear envio (C2). null/undefined = sem rate limit */\n rateLimitedUntil?: number | null\n /** Texto do banner de rate limit. Default: \"Aguarde {s}s antes de enviar nova mensagem\" */\n rateLimitBannerText?: (secondsRemaining: number) => string\n /** Modal de cota esgotada (C3) */\n quotaExhausted?: boolean\n quotaExhaustedConfig?: QuotaExhaustedConfig\n\n /* ─── Rating (J) ─── */\n /**\n * Callback disparado quando o usuario responde um rating de uma mensagem com `requestRating=true`.\n * Recebe:\n * - `messageId`: id da mensagem da IA que pediu avaliacao\n * - `value`: numero 1-5\n * - `label`: objeto com emoji/title/subtitle correspondente ao value\n *\n * O consumer tipicamente usa esses dados para adicionar uma nova mensagem do usuario no array\n * `messages` com o texto da escolha (ex: `label.subtitle` em PT-BR). O `MessageRating` some\n * imediatamente apos o clique (sem estado \"selecionado\" visual).\n */\n onRate?: (messageId: string, value: MessageRatingValue, label: RatingLabel) => void\n /**\n * Labels customizadas do `MessageRating` (sobrescreve PT-BR defaults).\n * Passado pro componente E usado no callback `onRate` ao montar o `label`.\n */\n ratingLabels?: Partial<MessageRatingLabels>\n\n /* ─── Configuracoes do MessageBar ─── */\n placeholder?: string\n maxLength?: number\n /** Modo so audio — passa pro MessageBar como state base */\n audioOnlyMode?: boolean\n\n /* ─── Limite de duracao da gravacao de audio ─── */\n /**\n * Limite de duracao da gravacao em segundos (configurado pelo consumer).\n * Quando atingido, o ChatThread auto-pausa a gravacao (vai pro paused state com\n * preview play/cancel/send) — a menos que `onMaxDurationReached` seja passado,\n * caso em que o consumer assume controle. Sem essa prop, sem limite.\n */\n maxRecordingDuration?: number\n /** Quantos segundos antes do limite ativar o warning visual (timer destructive\n * + label \"Xs restantes\"). Default 10. Ignorado sem `maxRecordingDuration`. */\n warnAtSecondsLeft?: number\n /** Label do warning. Default pt-BR: `(s) => \\`${s}s restantes\\``. */\n secondsLeftLabel?: (secondsLeft: number) => string\n /**\n * Callback opcional disparado ao atingir o limite. Se omitido, ChatThread\n * auto-pausa internamente. Se passado, **substitui** o auto-pause — o consumer\n * decide o que fazer (ex: mostrar toast antes de pausar manualmente).\n */\n onMaxDurationReached?: () => void\n\n /* ─── Identidade do usuario ─── */\n /** URL da imagem do avatar do usuario (aparece em todas as msgs do user no desktop). */\n userAvatar?: string\n /** Nome do usuario (usado para iniciais no fallback do avatar e alt da imagem). */\n userName?: string\n /** Classe de tema das bolhas do user (pass-through pro ChatMessage). Default \"theme-positive\". */\n userMessageTheme?: string\n\n /* ─── i18n / texto customizavel ─── */\n /** Default: \"Sem conexao. Tentando reconectar...\" */\n offlineBannerText?: string\n /** Default: \"Tentar novamente\" */\n retryButtonText?: string\n /** Default: \"Regenerar resposta\" */\n regenerateButtonText?: string\n /** Default: \"Parar resposta\" */\n stopResponseText?: string\n /** Default: \"Desculpe, nao consegui responder.\" */\n defaultAiErrorText?: string\n}\n\n/* ─── Constantes ──────────────────────────────────────────────── */\n\nconst SCROLL_BOTTOM_THRESHOLD = 100 // px do bottom pra considerar \"no fundo\"\nconst LOAD_MORE_THRESHOLD = 200 // px do topo pra disparar onLoadMore\n\n/* ─── Sub-componentes internos ────────────────────────────────── */\n\n/** Banner persistente no topo (offline, rate limit, etc.) */\nfunction ChatThreadBanner({\n variant,\n children,\n}: {\n variant: \"offline\" | \"rate-limit\"\n children: React.ReactNode\n}) {\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\n \"shrink-0 flex items-center gap-2 px-4 py-2 text-sm border-b\",\n variant === \"offline\" &&\n \"bg-destructive/10 text-destructive border-destructive/20\",\n variant === \"rate-limit\" &&\n \"bg-muted text-muted-foreground border-border\"\n )}\n >\n {children}\n </div>\n )\n}\n\n/** Conta segundos restantes ate `until` (timestamp epoch). */\nfunction useCountdown(until: number | null | undefined): number {\n const [now, setNow] = React.useState(() => Date.now())\n\n React.useEffect(() => {\n if (!until || until <= Date.now()) return\n const id = setInterval(() => setNow(Date.now()), 1000)\n return () => clearInterval(id)\n }, [until])\n\n if (!until) return 0\n const remainingMs = until - now\n return Math.max(0, Math.ceil(remainingMs / 1000))\n}\n\n/** Botao \"Parar resposta\" — substitui MessageBar durante thinking (F1) */\nfunction StopResponseButton({\n onClick,\n label = \"Parar resposta\",\n}: {\n onClick?: () => void\n label?: string\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className=\"w-full flex items-center justify-center gap-2 h-12 rounded-2xl bg-muted text-neutral-foreground hover:bg-muted/80 transition-colors focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n aria-label={label}\n >\n <Square className=\"size-4\" fill=\"currentColor\" strokeWidth={0} />\n <span className=\"text-sm font-medium\">{label}</span>\n </button>\n )\n}\n\n/** Bubble wrapper com animacao de entrada (E1: fade-in + slide-up 8px, 200ms) */\nfunction MessageBubbleAnimated({\n children,\n enableAnimation,\n}: {\n children: React.ReactNode\n enableAnimation: boolean\n}) {\n return (\n <div\n className={cn(\n \"transition-[opacity,transform] duration-200 ease-out\",\n enableAnimation ? \"animate-thread-msg-enter\" : \"\"\n )}\n >\n {children}\n </div>\n )\n}\n\n/* ─── Bottom slot: gerencia animacao sequencial entre MessageBar / StopButton / hidden ─── */\n\ntype BottomSlotKind = \"messagebar\" | \"stop\" | \"hidden\"\nconst LEAVE_DURATION_MS = 150\n\n/**\n * Slot que alterna entre 3 estados com animacao sequencial (sem cross-fade):\n * o conteudo atual anima saida (`thread-msg-leave`, 150ms), troca o conteudo,\n * e o novo anima entrada (`thread-msg-enter`, 200ms). Similar ao\n * AnimatePresence mode=\"wait\" do Framer Motion, mas em CSS puro.\n */\nfunction BottomSlot({\n kind,\n messageBar,\n stopButton,\n}: {\n kind: BottomSlotKind\n messageBar: React.ReactNode\n stopButton: React.ReactNode\n}) {\n const [rendered, setRendered] = React.useState<BottomSlotKind>(kind)\n const [phase, setPhase] = React.useState<\"enter\" | \"leave\">(\"enter\")\n\n React.useEffect(() => {\n if (rendered === kind) return\n setPhase(\"leave\")\n const t = setTimeout(() => {\n setRendered(kind)\n setPhase(\"enter\")\n }, LEAVE_DURATION_MS)\n return () => clearTimeout(t)\n }, [kind, rendered])\n\n return (\n <div className=\"shrink-0 px-4 pb-4 pt-2\">\n {rendered !== \"hidden\" && (\n <div\n key={rendered}\n className={\n phase === \"enter\" ? \"animate-thread-msg-enter\" : \"animate-thread-msg-leave\"\n }\n >\n {rendered === \"messagebar\" ? messageBar : stopButton}\n </div>\n )}\n </div>\n )\n}\n\n/* ─── Component principal ─────────────────────────────────────── */\n\nfunction ChatThread({\n messages,\n state = \"idle\",\n initialThinking = false,\n onSendText,\n onSendAudio,\n onRetryMessage,\n onRegenerateResponse,\n onStopResponse,\n onLoadMore,\n onStartRecording,\n onPauseRecording,\n onResumeRecording,\n onRecordingError,\n onCancelRecording,\n onTogglePlay,\n onSeekPlayback,\n recordingStream,\n recordingDuration,\n isPlaying,\n playbackProgress,\n offline = false,\n rateLimitedUntil = null,\n rateLimitBannerText,\n quotaExhausted = false,\n quotaExhaustedConfig,\n onRate,\n ratingLabels,\n placeholder,\n maxLength,\n audioOnlyMode = false,\n maxRecordingDuration,\n warnAtSecondsLeft,\n secondsLeftLabel,\n onMaxDurationReached,\n userAvatar,\n userName,\n userMessageTheme,\n offlineBannerText = \"Sem conexao. Tentando reconectar...\",\n retryButtonText = \"Tentar novamente\",\n regenerateButtonText = \"Regenerar resposta\",\n stopResponseText = \"Parar resposta\",\n defaultAiErrorText = \"Desculpe, nao consegui responder.\",\n className,\n ...props\n}: ChatThreadProps) {\n /* ─── State derivado ─── */\n const rateLimitSecondsLeft = useCountdown(rateLimitedUntil)\n const isRateLimited = rateLimitSecondsLeft > 0\n const isThinking =\n state === \"thinking\" || (initialThinking && messages.length === 0)\n const isDisabled = offline || isRateLimited || quotaExhausted\n\n /* ─── Refs e scroll inteligente (D1) ─── */\n const scrollRef = React.useRef<HTMLDivElement>(null)\n const [isAtBottom, setIsAtBottom] = React.useState(true)\n const prevMessageCountRef = React.useRef(messages.length)\n const loadMoreInFlightRef = React.useRef(false)\n const [isLoadingMore, setIsLoadingMore] = React.useState(false)\n // Marca de quando o componente montou — usada para suprimir animacao das mensagens iniciais\n const mountedRef = React.useRef(false)\n React.useEffect(() => {\n mountedRef.current = true\n }, [])\n\n const checkScrollPosition = React.useCallback(() => {\n const el = scrollRef.current\n if (!el) return\n const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight\n const atBottom = distanceFromBottom <= SCROLL_BOTTOM_THRESHOLD\n setIsAtBottom(atBottom)\n }, [])\n\n const scrollToBottom = React.useCallback((smooth = true) => {\n const el = scrollRef.current\n if (!el) return\n el.scrollTo({\n top: el.scrollHeight,\n behavior: smooth ? \"smooth\" : \"auto\",\n })\n }, [])\n\n /* ─── Auto-scroll quando chega nova mensagem ─── */\n React.useEffect(() => {\n const prev = prevMessageCountRef.current\n const curr = messages.length\n prevMessageCountRef.current = curr\n\n if (curr <= prev) return\n\n // Esta no fundo → scroll suave pra nova msg. Se o usuario subiu, preserva a\n // posicao (sem botao flutuante — ele sobrepunha o MessageBar).\n if (isAtBottom) {\n requestAnimationFrame(() => scrollToBottom(true))\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [messages.length])\n\n /* ─── Scroll handler (detecta posicao + paginacao) ─── */\n React.useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n const handler = () => {\n checkScrollPosition()\n // Paginacao: se chegou perto do topo e ha onLoadMore, dispara\n if (\n onLoadMore &&\n !loadMoreInFlightRef.current &&\n el.scrollTop <= LOAD_MORE_THRESHOLD\n ) {\n loadMoreInFlightRef.current = true\n setIsLoadingMore(true)\n const prevScrollHeight = el.scrollHeight\n Promise.resolve(onLoadMore())\n .then(() => {\n // Preserva posicao visual apos prepend de mensagens antigas\n requestAnimationFrame(() => {\n const newScrollHeight = el.scrollHeight\n el.scrollTop = newScrollHeight - prevScrollHeight\n })\n })\n .finally(() => {\n loadMoreInFlightRef.current = false\n setIsLoadingMore(false)\n })\n }\n }\n el.addEventListener(\"scroll\", handler, { passive: true })\n // Inicial: garante scroll no fundo\n requestAnimationFrame(() => scrollToBottom(false))\n return () => el.removeEventListener(\"scroll\", handler)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [onLoadMore])\n\n /* ─── Aria-live announce de nova msg da IA (H1) ─── */\n const lastAiMessage = React.useMemo(() => {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].persona === \"ai\") return messages[i]\n }\n return null\n }, [messages])\n\n /* ─── Rating per-message: rastreia IDs cujo rating foi respondido (auto-dismiss imediato) ─── */\n const [ratedMessageIds, setRatedMessageIds] = React.useState<Set<string>>(() => new Set())\n\n // Merge dos labels customizados com defaults PT-BR. Usado pra renderizar E pra montar o callback.\n const mergedRatingLabels: MessageRatingLabels = React.useMemo(\n () => ({ ...DEFAULT_MESSAGE_RATING_LABELS, ...ratingLabels }),\n [ratingLabels]\n )\n\n const handleRateMessage = React.useCallback(\n (messageId: string, value: MessageRatingValue) => {\n // Marca como respondido IMEDIATAMENTE — re-render remove o rating do DOM antes\n // do paint visual de \"selected\", entao o usuario nao ve o estado intermediario.\n setRatedMessageIds((prev) => {\n const next = new Set(prev)\n next.add(messageId)\n return next\n })\n // Dispara callback com label completo (emoji/title/subtitle).\n onRate?.(messageId, value, mergedRatingLabels[value])\n },\n [onRate, mergedRatingLabels]\n )\n\n /* ─── MessageBar state interno (state machine de gravacao) ─── */\n const baseMessageBarState: MessageBarState = audioOnlyMode ? \"audio-only\" : \"default\"\n const [internalRecordingState, setInternalRecordingState] = React.useState<\n \"idle\" | \"requesting\" | \"recording\" | \"paused\"\n >(\"idle\")\n // Token do pedido de microfone em voo — descarta resolucao de pedidos antigos (ex.: cancelou/desmontou no meio)\n const recordingRequestTokenRef = React.useRef(0)\n const isMountedRef = React.useRef(false)\n React.useEffect(() => {\n isMountedRef.current = true\n return () => {\n isMountedRef.current = false\n recordingRequestTokenRef.current++\n }\n }, [])\n\n // Reset pra idle quando muda audioOnlyMode e nao esta gravando\n React.useEffect(() => {\n if (internalRecordingState === \"idle\") return\n // mantem o state se gravacao em andamento\n }, [audioOnlyMode, internalRecordingState])\n\n // State final do MessageBar — combina base, recording state e disabled\n const messageBarState: MessageBarState = isDisabled\n ? \"disabled\"\n : internalRecordingState === \"recording\"\n ? \"recording\"\n : internalRecordingState === \"paused\"\n ? \"paused\"\n : internalRecordingState === \"requesting\"\n ? \"requesting\"\n : baseMessageBarState\n\n /** Roda o callback de start/resume. Se devolver Promise, segura o MessageBar em\n * `requesting` ate resolver; ao rejeitar, volta pro estado anterior e avisa o consumer. */\n const runRecordingRequest = React.useCallback(\n (\n callback: (() => void | Promise<unknown>) | undefined,\n fallbackState: \"idle\" | \"paused\"\n ) => {\n const result = callback?.()\n if (!isThenable(result)) {\n setInternalRecordingState(\"recording\")\n return\n }\n const token = ++recordingRequestTokenRef.current\n setInternalRecordingState(\"requesting\")\n result.then(\n () => {\n if (!isMountedRef.current || recordingRequestTokenRef.current !== token) return\n setInternalRecordingState(\"recording\")\n },\n (error: unknown) => {\n if (!isMountedRef.current || recordingRequestTokenRef.current !== token) return\n setInternalRecordingState(fallbackState)\n onRecordingError?.(error)\n }\n )\n },\n [onRecordingError]\n )\n\n /* Wrappers dos callbacks: atualizam state interno + chamam consumer */\n const handleStartRecordingInternal = React.useCallback(() => {\n runRecordingRequest(onStartRecording, \"idle\")\n }, [onStartRecording, runRecordingRequest])\n\n const handlePauseRecordingInternal = React.useCallback(() => {\n setInternalRecordingState(\"paused\")\n onPauseRecording?.()\n }, [onPauseRecording])\n\n const handleResumeRecordingInternal = React.useCallback(() => {\n runRecordingRequest(onResumeRecording, \"paused\")\n }, [onResumeRecording, runRecordingRequest])\n\n const handleCancelRecordingInternal = React.useCallback(() => {\n recordingRequestTokenRef.current++ // invalida pedido de microfone em voo\n setInternalRecordingState(\"idle\")\n onCancelRecording?.()\n }, [onCancelRecording])\n\n const handleSendAudioInternal = React.useCallback(() => {\n recordingRequestTokenRef.current++\n setInternalRecordingState(\"idle\")\n onSendAudio?.()\n }, [onSendAudio])\n\n /* Auto-pause ao atingir limite de duracao. Se consumer passou onMaxDurationReached,\n delega controle pra ele (sem auto-pause). */\n const handleMaxDurationInternal = React.useCallback(() => {\n if (onMaxDurationReached) {\n onMaxDurationReached()\n } else {\n handlePauseRecordingInternal()\n }\n }, [onMaxDurationReached, handlePauseRecordingInternal])\n\n /* Input controlled — sem isso o MessageBar fica \"engessado\" em value=\"\"\n (o consumer recebe o texto via onSendText, mas nao precisa controlar o input). */\n const [inputValue, setInputValue] = React.useState(\"\")\n const handleSendTextInternal = React.useCallback(\n (text: string) => {\n setInputValue(\"\")\n onSendText?.(text)\n },\n [onSendText]\n )\n\n /* ─── Render ──────────────────────────────────────── */\n return (\n <div\n data-slot=\"chat-thread\"\n className={cn(\"relative flex flex-col h-full bg-background\", className)}\n {...props}\n >\n {/* Banners de erro (topo) */}\n {offline && (\n <ChatThreadBanner variant=\"offline\">\n <span aria-hidden=\"true\">⚠</span>\n <span>{offlineBannerText}</span>\n </ChatThreadBanner>\n )}\n {isRateLimited && (\n <ChatThreadBanner variant=\"rate-limit\">\n <span aria-hidden=\"true\">⏱</span>\n <span>\n {rateLimitBannerText\n ? rateLimitBannerText(rateLimitSecondsLeft)\n : `Aguarde ${rateLimitSecondsLeft}s antes de enviar nova mensagem`}\n </span>\n </ChatThreadBanner>\n )}\n\n {/* Thread scrollavel */}\n <div\n ref={scrollRef}\n // Scrollbar oculta (Firefox + WebKit/Chromium); scroll segue funcionando\n className=\"flex-1 overflow-y-auto px-4 py-4 space-y-4 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n role=\"log\"\n aria-live=\"polite\"\n aria-atomic=\"false\"\n >\n {/* Spinner de paginacao no topo (G2) */}\n {isLoadingMore && (\n <div className=\"flex justify-center py-2\" aria-label=\"Carregando mensagens anteriores\">\n <span className=\"inline-block size-5 border-2 border-muted-foreground/30 border-t-muted-foreground rounded-full animate-spin\" />\n </div>\n )}\n\n {messages.map((msg, idx) => {\n // Suprime animacao das mensagens que ja existiam no mount inicial\n const enableAnim =\n mountedRef.current && idx >= prevMessageCountRef.current - 1\n\n const isFailedUser = msg.persona === \"user\" && msg.status === \"failed\"\n const isFailedAi = msg.persona === \"ai\" && msg.status === \"failed\"\n const isPendingUser = msg.persona === \"user\" && msg.status === \"pending\"\n\n return (\n <MessageBubbleAnimated key={msg.id} enableAnimation={enableAnim}>\n <ChatMessage\n persona={msg.persona}\n text={msg.text}\n audioSrc={msg.audioSrc}\n avatar={msg.persona === \"user\" ? userAvatar : undefined}\n author={msg.persona === \"user\" ? userName : undefined}\n theme={userMessageTheme}\n // Indicador de pending: typing dots no lugar do conteudo normal e visual sutil\n loading={isPendingUser && !msg.text && !msg.audioSrc}\n className={cn(\n isPendingUser && \"opacity-80\",\n isFailedUser && \"[&_[data-slot=chat-message-bubble]]:border-2 [&_[data-slot=chat-message-bubble]]:border-destructive\"\n )}\n />\n {/* Retry inline pra msg do user que falhou (B2) */}\n {isFailedUser && (\n <div className=\"mt-2 flex justify-end\">\n <button\n type=\"button\"\n onClick={() => onRetryMessage?.(msg.id)}\n className=\"inline-flex items-center gap-1 text-xs text-destructive hover:underline focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 rounded\"\n >\n ↻ {retryButtonText}\n </button>\n </div>\n )}\n {/* Bubble de erro pra resposta da IA (B3) */}\n {isFailedAi && (\n /* Alinhado com a bubble da IA: offset = badge 32px + gap 12px = 44px (desktop).\n Mobile sem offset porque o badge fica oculto no ChatMessage AI. */\n <div className=\"mt-2 sm:pl-11 flex flex-col items-start gap-2\">\n <div className=\"text-sm text-destructive\">\n {msg.errorText || defaultAiErrorText}\n </div>\n <button\n type=\"button\"\n onClick={onRegenerateResponse}\n className=\"inline-flex items-center gap-1 text-xs text-primary hover:underline focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 rounded\"\n >\n ↻ {regenerateButtonText}\n </button>\n </div>\n )}\n {/* MessageRating inline na mensagem da IA que pede avaliacao (J).\n Alinhado com a bubble da IA (offset 44px desktop, 0 mobile).\n value={null} mantem o componente sem estado \"selecionado\" visual —\n ao clicar, o ChatThread marca como rated imediatamente (re-render remove\n do DOM antes do paint), e o consumer recebe (messageId, value, label). */}\n {msg.persona === \"ai\" && msg.requestRating && !ratedMessageIds.has(msg.id) && (\n <div className=\"mt-3 sm:pl-11 animate-thread-msg-enter\">\n <MessageRating\n value={null}\n labels={ratingLabels}\n onChange={(value) => handleRateMessage(msg.id, value)}\n />\n </div>\n )}\n </MessageBubbleAnimated>\n )\n })}\n\n {/* Typing indicator da IA durante thinking */}\n {isThinking && (\n <MessageBubbleAnimated enableAnimation>\n <ChatMessage persona=\"ai\" loading />\n </MessageBubbleAnimated>\n )}\n\n {/* Aria-live oculto pra anunciar ultima msg da IA */}\n <div className=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\">\n {lastAiMessage?.text || \"\"}\n </div>\n </div>\n\n {/* Bottom area: MessageBar / StopResponse / hidden.\n O botao \"Parar resposta\" so aparece durante thinking E quando o consumer passa\n `onStopResponse` — quem nao implementa cancelamento ve o MessageBar sumir com\n animacao de saida e voltar com animacao de entrada quando a IA termina. */}\n <BottomSlot\n kind={\n isThinking\n ? onStopResponse\n ? \"stop\"\n : \"hidden\"\n : \"messagebar\"\n }\n messageBar={\n <MessageBar\n state={messageBarState}\n value={inputValue}\n onChange={setInputValue}\n onSendText={handleSendTextInternal}\n onSendAudio={handleSendAudioInternal}\n onStartRecording={handleStartRecordingInternal}\n onPauseRecording={handlePauseRecordingInternal}\n onResumeRecording={handleResumeRecordingInternal}\n onCancelRecording={handleCancelRecordingInternal}\n onTogglePlay={onTogglePlay}\n onSeekPlayback={onSeekPlayback}\n recordingStream={recordingStream}\n recordingDuration={recordingDuration}\n isPlaying={isPlaying}\n playbackProgress={playbackProgress}\n placeholder={placeholder}\n maxRecordingDuration={maxRecordingDuration}\n warnAtSecondsLeft={warnAtSecondsLeft}\n secondsLeftLabel={secondsLeftLabel}\n onMaxDurationReached={handleMaxDurationInternal}\n />\n }\n stopButton={\n <StopResponseButton onClick={onStopResponse} label={stopResponseText} />\n }\n />\n\n {/* Quota exhausted modal (C3) */}\n {quotaExhausted && (\n <AlertDialog open>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {quotaExhaustedConfig?.title || \"Limite atingido\"}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {quotaExhaustedConfig?.description ||\n \"Voce atingiu o limite de mensagens do seu plano. Faca upgrade para continuar.\"}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n {quotaExhaustedConfig?.onCancel && (\n <AlertDialogCancel onClick={quotaExhaustedConfig.onCancel}>\n {quotaExhaustedConfig.cancelLabel || \"Cancelar\"}\n </AlertDialogCancel>\n )}\n <AlertDialogAction onClick={quotaExhaustedConfig?.onCta}>\n {quotaExhaustedConfig?.ctaLabel || \"Fazer upgrade\"}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )}\n </div>\n )\n}\n\nexport { ChatThread }\n"]}
@@ -54,12 +54,23 @@ interface ChatThreadProps extends Omit<React.ComponentProps<"div">, "onChange">
54
54
  onStopResponse?: () => void;
55
55
  /** Carregar mensagens antigas (G1 — scroll infinito) */
56
56
  onLoadMore?: () => Promise<void> | void;
57
- /** Dispara quando o user pressiona o mic. Consumer chama getUserMedia + MediaRecorder. */
58
- onStartRecording?: () => void;
57
+ /** Dispara quando o user pressiona o mic. Consumer chama getUserMedia + MediaRecorder.
58
+ * Pode retornar uma Promise: enquanto ela estiver pendente o MessageBar fica em
59
+ * `requesting` (mic com spinner) e so entra em `recording` quando resolver. Se
60
+ * rejeitar, volta pro estado anterior e dispara `onRecordingError`. Retorno sincrono
61
+ * (void) mantem o comportamento antigo: entra em `recording` imediatamente.
62
+ * Recomendado: `onStartRecording={() => startRecording()}` retornando a Promise do
63
+ * getUserMedia — evita mostrar "gravando" antes do microfone existir. */
64
+ onStartRecording?: () => void | Promise<unknown>;
59
65
  /** Dispara quando o user pausa a gravacao. Consumer faz recorder.stop() + cria Blob. */
60
66
  onPauseRecording?: () => void;
61
- /** Dispara quando o user retoma gravacao apos pause. */
62
- onResumeRecording?: () => void;
67
+ /** Dispara quando o user retoma gravacao apos pause. Mesma semantica de Promise do
68
+ * `onStartRecording` (rejeitou → volta pra `paused`). */
69
+ onResumeRecording?: () => void | Promise<unknown>;
70
+ /** Dispara quando a Promise de `onStartRecording`/`onResumeRecording` rejeita (ex.:
71
+ * permissao de microfone negada). O ChatThread ja restaurou o estado do MessageBar;
72
+ * o consumer decide o feedback (toast, alert, banner). */
73
+ onRecordingError?: (error: unknown) => void;
63
74
  /** Dispara quando o user cancela a gravacao. Consumer cleanup do MediaRecorder. */
64
75
  onCancelRecording?: () => void;
65
76
  /** Toggle play/pause do audio gravado durante o estado `paused` do MessageBar. */
@@ -126,6 +137,8 @@ interface ChatThreadProps extends Omit<React.ComponentProps<"div">, "onChange">
126
137
  userAvatar?: string;
127
138
  /** Nome do usuario (usado para iniciais no fallback do avatar e alt da imagem). */
128
139
  userName?: string;
140
+ /** Classe de tema das bolhas do user (pass-through pro ChatMessage). Default "theme-positive". */
141
+ userMessageTheme?: string;
129
142
  /** Default: "Sem conexao. Tentando reconectar..." */
130
143
  offlineBannerText?: string;
131
144
  /** Default: "Tentar novamente" */
@@ -137,6 +150,6 @@ interface ChatThreadProps extends Omit<React.ComponentProps<"div">, "onChange">
137
150
  /** Default: "Desculpe, nao consegui responder." */
138
151
  defaultAiErrorText?: string;
139
152
  }
140
- declare function ChatThread({ messages, state, initialThinking, onSendText, onSendAudio, onRetryMessage, onRegenerateResponse, onStopResponse, onLoadMore, onStartRecording, onPauseRecording, onResumeRecording, onCancelRecording, onTogglePlay, onSeekPlayback, recordingStream, recordingDuration, isPlaying, playbackProgress, offline, rateLimitedUntil, rateLimitBannerText, quotaExhausted, quotaExhaustedConfig, onRate, ratingLabels, placeholder, maxLength, audioOnlyMode, maxRecordingDuration, warnAtSecondsLeft, secondsLeftLabel, onMaxDurationReached, userAvatar, userName, offlineBannerText, retryButtonText, regenerateButtonText, stopResponseText, defaultAiErrorText, className, ...props }: ChatThreadProps): react_jsx_runtime.JSX.Element;
153
+ declare function ChatThread({ messages, state, initialThinking, onSendText, onSendAudio, onRetryMessage, onRegenerateResponse, onStopResponse, onLoadMore, onStartRecording, onPauseRecording, onResumeRecording, onRecordingError, onCancelRecording, onTogglePlay, onSeekPlayback, recordingStream, recordingDuration, isPlaying, playbackProgress, offline, rateLimitedUntil, rateLimitBannerText, quotaExhausted, quotaExhaustedConfig, onRate, ratingLabels, placeholder, maxLength, audioOnlyMode, maxRecordingDuration, warnAtSecondsLeft, secondsLeftLabel, onMaxDurationReached, userAvatar, userName, userMessageTheme, offlineBannerText, retryButtonText, regenerateButtonText, stopResponseText, defaultAiErrorText, className, ...props }: ChatThreadProps): react_jsx_runtime.JSX.Element;
141
154
 
142
155
  export { ChatThread, type ChatThreadMessage, type ChatThreadMessageStatus, type ChatThreadPersona, type ChatThreadProps, type ChatThreadState, type QuotaExhaustedConfig };
@@ -1,11 +1,12 @@
1
1
  "use client";
2
- export { ChatThread } from '../chunk-EAVNA2RW.js';
2
+ export { ChatThread } from '../chunk-MQX4NBKQ.js';
3
3
  import '../chunk-2KFWBA76.js';
4
- import '../chunk-VUVC2NAK.js';
4
+ import '../chunk-KSEDDFGG.js';
5
5
  import '../chunk-RPU3FY62.js';
6
6
  import '../chunk-YIOXPV2N.js';
7
- import '../chunk-TK67WF35.js';
7
+ import '../chunk-5AH2PNYW.js';
8
8
  import '../chunk-T2TJG24M.js';
9
+ import '../chunk-TDBDX5NV.js';
9
10
  import '../chunk-N2F3ZG2S.js';
10
11
  import '../chunk-KLX547KZ.js';
11
12
  import '../chunk-US5LXJTO.js';
package/dist/index.js CHANGED
@@ -18,12 +18,12 @@ export { VideoPlayer } from './chunk-QXZRH3HU.js';
18
18
  export { playerLocales } from './chunk-EFT7BHN7.js';
19
19
  export { Toaster, cycleToast } from './chunk-7ZSICZXA.js';
20
20
  export { Alert, AlertAction, AlertClose, AlertDescription, AlertTitle, alertVariants } from './chunk-PS3MV5G7.js';
21
- export { ChatThread } from './chunk-EAVNA2RW.js';
21
+ export { ChatThread } from './chunk-MQX4NBKQ.js';
22
22
  export { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger } from './chunk-2KFWBA76.js';
23
- export { ChatAudio, ChatMessage, chatMessageVariants } from './chunk-VUVC2NAK.js';
23
+ export { ChatAudio, ChatMessage, chatMessageVariants } from './chunk-KSEDDFGG.js';
24
24
  export { LikeDislike, likeDislikeVariants } from './chunk-RPU3FY62.js';
25
25
  export { MessageRating } from './chunk-YIOXPV2N.js';
26
- export { MessageBar } from './chunk-TK67WF35.js';
26
+ export { MessageBar } from './chunk-5AH2PNYW.js';
27
27
  export { SelectorBar } from './chunk-SKBY6OZL.js';
28
28
  export { ChatPanel } from './chunk-OLYW5LVG.js';
29
29
  import './chunk-7FJ7LRSV.js';
@@ -31,6 +31,13 @@ interface ChatMessageProps extends Omit<React.ComponentProps<"div">, "content">,
31
31
  onFeedbackChange?: (value: LikeDislikeValue) => void;
32
32
  /** Estado de loading: mostra typing indicator ("...") */
33
33
  loading?: boolean;
34
+ /**
35
+ * Classe de tema da bolha do user (bubble, audio e badge de velocidade herdam
36
+ * `bg-accent`/`bg-primary` dela). Default `"theme-positive"` — verde, leitura
37
+ * familiar de "mensagem enviada" (WhatsApp); nao colide com critical nem disputa
38
+ * com os botoes brand. So tem efeito em `persona="user"`. Ex.: `theme="theme-class"`.
39
+ */
40
+ theme?: string;
34
41
  }
35
42
  interface ChatAudioProps extends React.ComponentProps<"div"> {
36
43
  /** URL do audio */
@@ -38,8 +45,9 @@ interface ChatAudioProps extends React.ComponentProps<"div"> {
38
45
  /**
39
46
  * Variante visual:
40
47
  * - `neutral`: container `bg-muted` (cinza claro) — usado em AI messages
41
- * - `brand`: container `bg-accent` (rosa pastel) — usado em User messages
42
- * (requer wrapper `.theme-brand` no componente pai)
48
+ * - `brand`: container `bg-accent` tematizado — usado em User messages.
49
+ * A cor vem do wrapper `.theme-*` do componente pai (ChatMessage aplica
50
+ * `theme-positive` por default; nome mantido por retrocompatibilidade)
43
51
  */
44
52
  variant?: "neutral" | "brand";
45
53
  /**
@@ -57,6 +65,6 @@ interface ChatAudioProps extends React.ComponentProps<"div"> {
57
65
  }
58
66
  declare function ChatAudio({ src, variant, peaks: providedPeaks, barCount, className, ...props }: ChatAudioProps): react_jsx_runtime.JSX.Element;
59
67
 
60
- declare function ChatMessage({ persona, text, audioSrc, avatar, author, showFeedback, feedbackValue, onFeedbackChange, loading, className, ...props }: ChatMessageProps): react_jsx_runtime.JSX.Element;
68
+ declare function ChatMessage({ persona, text, audioSrc, avatar, author, showFeedback, feedbackValue, onFeedbackChange, loading, theme, className, ...props }: ChatMessageProps): react_jsx_runtime.JSX.Element;
61
69
 
62
70
  export { ChatAudio, type ChatAudioProps, ChatMessage, type ChatMessageProps, chatMessageVariants };
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- export { ChatAudio, ChatMessage, chatMessageVariants } from '../chunk-VUVC2NAK.js';
2
+ export { ChatAudio, ChatMessage, chatMessageVariants } from '../chunk-KSEDDFGG.js';
3
3
  import '../chunk-RPU3FY62.js';
4
4
  import '../chunk-T2TJG24M.js';
5
5
  import '../chunk-N2F3ZG2S.js';
@@ -9,8 +9,11 @@ import * as React from 'react';
9
9
  * - `disabled`: input desabilitado
10
10
  * - `recording`: barra de gravacao (timer + waveform live + delete/pause/send)
11
11
  * - `paused`: gravacao pausada (timer + waveform + play/delete/mic/send)
12
+ * - `requesting`: aguardando o consumer obter o microfone (getUserMedia pendente) —
13
+ * mic vira spinner desabilitado. O ChatThread entra aqui quando `onStartRecording`
14
+ * / `onResumeRecording` retornam Promise; consumers diretos podem usar tambem.
12
15
  */
13
- type MessageBarState = "default" | "audio-only" | "active" | "disabled" | "recording" | "paused";
16
+ type MessageBarState = "default" | "audio-only" | "active" | "disabled" | "recording" | "paused" | "requesting";
14
17
  interface MessageBarProps extends Omit<React.ComponentProps<"div">, "onChange"> {
15
18
  /** Estado visual do componente (controlado pelo consumer) */
16
19
  state?: MessageBarState;
@@ -54,6 +57,10 @@ interface MessageBarProps extends Omit<React.ComponentProps<"div">, "onChange">
54
57
  * Progresso do playback do audio gravado (0-1), usado durante state=paused.
55
58
  * Quando fornecido, o waveform mostra barras tocadas/nao tocadas + dot verde
56
59
  * sincronizado. Sem isso, o waveform mostra todas as barras como nao tocadas.
60
+ *
61
+ * Alimente via `requestAnimationFrame` enquanto o audio toca, NAO so via
62
+ * `timeupdate` — esse evento dispara ~4x/s (250ms) e o dot pula em stop motion.
63
+ * Ver exemplo na doc do MessageBar.
57
64
  */
58
65
  playbackProgress?: number;
59
66
  /**
@@ -1,5 +1,8 @@
1
1
  "use client";
2
- export { MessageBar } from '../chunk-TK67WF35.js';
2
+ export { MessageBar } from '../chunk-5AH2PNYW.js';
3
+ import '../chunk-TDBDX5NV.js';
4
+ import '../chunk-KLX547KZ.js';
5
+ import '../chunk-US5LXJTO.js';
3
6
  import '../chunk-LYTC7UMN.js';
4
7
  import '../chunk-W4KJCBMW.js';
5
8
  import '../chunk-2NMEKWO5.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluencypassdevs/cycle",
3
- "version": "1.27.2",
3
+ "version": "1.29.0",
4
4
  "description": "Cycle Design System — UI component library by Fluencypass (produto)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/composites/chat-thread.tsx"],"names":[],"mappings":"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","file":"chunk-EAVNA2RW.js","sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronDown, Square } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { ChatMessage } from \"@/components/ui/chat-message\"\nimport {\n MessageBar,\n type MessageBarState,\n} from \"@/components/ui/message-bar\"\nimport {\n MessageRating,\n DEFAULT_MESSAGE_RATING_LABELS,\n type MessageRatingValue,\n type MessageRatingLabels,\n type RatingLabel,\n} from \"@/components/ui/message-rating\"\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from \"@/components/ui/alert-dialog\"\n\n/* ─── Types ───────────────────────────────────────────────────── */\n\nexport type ChatThreadMessageStatus = \"sent\" | \"pending\" | \"failed\"\nexport type ChatThreadPersona = \"ai\" | \"user\" | \"system\"\n\nexport interface ChatThreadMessage {\n id: string\n persona: ChatThreadPersona\n status?: ChatThreadMessageStatus\n text?: string\n audioSrc?: string\n audioPeaks?: number[]\n /** Mensagem de erro pra exibir quando `persona=\"ai\"` e `status=\"failed\"` */\n errorText?: string\n /**\n * Quando true em uma mensagem da IA, renderiza o `MessageRating` (5 emojis) logo\n * abaixo da bubble — alinhado com a IA, parte da mesma \"linha visual\" da mensagem.\n * Apos o usuario responder, o rating some automaticamente (fade-out + slide-down).\n *\n * Usado quando a IA pede avaliacao da atividade: o consumer adiciona a msg da IA\n * com este campo `true` no array `messages` e ouve `onRate` para receber a resposta.\n */\n requestRating?: boolean\n}\n\nexport type ChatThreadState = \"idle\" | \"sending\" | \"thinking\" | \"error\"\n\nexport interface QuotaExhaustedConfig {\n title?: string\n description?: string\n ctaLabel?: string\n cancelLabel?: string\n onCta?: () => void\n onCancel?: () => void\n}\n\nexport interface ChatThreadProps extends Omit<React.ComponentProps<\"div\">, \"onChange\"> {\n /** Lista de mensagens da conversa */\n messages: ChatThreadMessage[]\n /** Estado global da conversa (controlled) */\n state?: ChatThreadState\n /** Quando true e thread vazio, monta direto em thinking (cenario Class — IA fala primeiro) */\n initialThinking?: boolean\n\n /* ─── Callbacks essenciais ─── */\n onSendText?: (text: string) => void\n /** Disparado quando user confirma envio do audio gravado (MessageBar dispara apos release/lock).\n * Consumer gerencia MediaRecorder e tem acesso ao Blob no momento que isso for chamado. */\n onSendAudio?: () => void\n /** Retry de mensagem do usuario que falhou enviar (B2) */\n onRetryMessage?: (messageId: string) => void\n /** Regenerar resposta da IA com erro (B3) */\n onRegenerateResponse?: () => void\n /** Cancelar resposta da IA mid-thinking (F1). Passar este callback **ativa** o botao\n * \"Parar resposta\" no rodape durante o estado `thinking`. Se omitido, o MessageBar\n * some com animacao de saida quando a IA esta pensando e volta com animacao de entrada\n * quando termina — o consumer indica que nao quer expor cancelamento. */\n onStopResponse?: () => void\n /** Carregar mensagens antigas (G1 — scroll infinito) */\n onLoadMore?: () => Promise<void> | void\n\n /* ─── Callbacks do MessageBar (pass-through) ─── */\n /** Dispara quando o user pressiona o mic. Consumer chama getUserMedia + MediaRecorder. */\n onStartRecording?: () => void\n /** Dispara quando o user pausa a gravacao. Consumer faz recorder.stop() + cria Blob. */\n onPauseRecording?: () => void\n /** Dispara quando o user retoma gravacao apos pause. */\n onResumeRecording?: () => void\n /** Dispara quando o user cancela a gravacao. Consumer cleanup do MediaRecorder. */\n onCancelRecording?: () => void\n /** Toggle play/pause do audio gravado durante o estado `paused` do MessageBar. */\n onTogglePlay?: () => void\n /** Callback de seek no waveform do audio gravado (durante paused). */\n onSeekPlayback?: (progress: number) => void\n\n /* ─── State do MessageBar (pass-through) ─── */\n /** Stream do microfone durante recording — usado pelo MessageBar pra renderizar o live waveform. */\n recordingStream?: MediaStream | null\n /** Duracao da gravacao em segundos (controlado pelo consumer via setInterval). */\n recordingDuration?: number\n /** Indica se o audio gravado esta tocando (durante paused). */\n isPlaying?: boolean\n /** Progresso de playback (0-1) — usado pra sincronizar dot verde no waveform. */\n playbackProgress?: number\n\n /* ─── Erro states (configuravel) ─── */\n /** Mostra banner de offline + disabled MessageBar (C1) */\n offline?: boolean\n /** Timestamp epoch ate quando bloquear envio (C2). null/undefined = sem rate limit */\n rateLimitedUntil?: number | null\n /** Texto do banner de rate limit. Default: \"Aguarde {s}s antes de enviar nova mensagem\" */\n rateLimitBannerText?: (secondsRemaining: number) => string\n /** Modal de cota esgotada (C3) */\n quotaExhausted?: boolean\n quotaExhaustedConfig?: QuotaExhaustedConfig\n\n /* ─── Rating (J) ─── */\n /**\n * Callback disparado quando o usuario responde um rating de uma mensagem com `requestRating=true`.\n * Recebe:\n * - `messageId`: id da mensagem da IA que pediu avaliacao\n * - `value`: numero 1-5\n * - `label`: objeto com emoji/title/subtitle correspondente ao value\n *\n * O consumer tipicamente usa esses dados para adicionar uma nova mensagem do usuario no array\n * `messages` com o texto da escolha (ex: `label.subtitle` em PT-BR). O `MessageRating` some\n * imediatamente apos o clique (sem estado \"selecionado\" visual).\n */\n onRate?: (messageId: string, value: MessageRatingValue, label: RatingLabel) => void\n /**\n * Labels customizadas do `MessageRating` (sobrescreve PT-BR defaults).\n * Passado pro componente E usado no callback `onRate` ao montar o `label`.\n */\n ratingLabels?: Partial<MessageRatingLabels>\n\n /* ─── Configuracoes do MessageBar ─── */\n placeholder?: string\n maxLength?: number\n /** Modo so audio — passa pro MessageBar como state base */\n audioOnlyMode?: boolean\n\n /* ─── Limite de duracao da gravacao de audio ─── */\n /**\n * Limite de duracao da gravacao em segundos (configurado pelo consumer).\n * Quando atingido, o ChatThread auto-pausa a gravacao (vai pro paused state com\n * preview play/cancel/send) — a menos que `onMaxDurationReached` seja passado,\n * caso em que o consumer assume controle. Sem essa prop, sem limite.\n */\n maxRecordingDuration?: number\n /** Quantos segundos antes do limite ativar o warning visual (timer destructive\n * + label \"Xs restantes\"). Default 10. Ignorado sem `maxRecordingDuration`. */\n warnAtSecondsLeft?: number\n /** Label do warning. Default pt-BR: `(s) => \\`${s}s restantes\\``. */\n secondsLeftLabel?: (secondsLeft: number) => string\n /**\n * Callback opcional disparado ao atingir o limite. Se omitido, ChatThread\n * auto-pausa internamente. Se passado, **substitui** o auto-pause — o consumer\n * decide o que fazer (ex: mostrar toast antes de pausar manualmente).\n */\n onMaxDurationReached?: () => void\n\n /* ─── Identidade do usuario ─── */\n /** URL da imagem do avatar do usuario (aparece em todas as msgs do user no desktop). */\n userAvatar?: string\n /** Nome do usuario (usado para iniciais no fallback do avatar e alt da imagem). */\n userName?: string\n\n /* ─── i18n / texto customizavel ─── */\n /** Default: \"Sem conexao. Tentando reconectar...\" */\n offlineBannerText?: string\n /** Default: \"Tentar novamente\" */\n retryButtonText?: string\n /** Default: \"Regenerar resposta\" */\n regenerateButtonText?: string\n /** Default: \"Parar resposta\" */\n stopResponseText?: string\n /** Default: \"Desculpe, nao consegui responder.\" */\n defaultAiErrorText?: string\n}\n\n/* ─── Constantes ──────────────────────────────────────────────── */\n\nconst SCROLL_BOTTOM_THRESHOLD = 100 // px do bottom pra considerar \"no fundo\"\nconst LOAD_MORE_THRESHOLD = 200 // px do topo pra disparar onLoadMore\n\n/* ─── Sub-componentes internos ────────────────────────────────── */\n\n/** Banner persistente no topo (offline, rate limit, etc.) */\nfunction ChatThreadBanner({\n variant,\n children,\n}: {\n variant: \"offline\" | \"rate-limit\"\n children: React.ReactNode\n}) {\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\n \"shrink-0 flex items-center gap-2 px-4 py-2 text-sm border-b\",\n variant === \"offline\" &&\n \"bg-destructive/10 text-destructive border-destructive/20\",\n variant === \"rate-limit\" &&\n \"bg-muted text-muted-foreground border-border\"\n )}\n >\n {children}\n </div>\n )\n}\n\n/** Conta segundos restantes ate `until` (timestamp epoch). */\nfunction useCountdown(until: number | null | undefined): number {\n const [now, setNow] = React.useState(() => Date.now())\n\n React.useEffect(() => {\n if (!until || until <= Date.now()) return\n const id = setInterval(() => setNow(Date.now()), 1000)\n return () => clearInterval(id)\n }, [until])\n\n if (!until) return 0\n const remainingMs = until - now\n return Math.max(0, Math.ceil(remainingMs / 1000))\n}\n\n/** Botao \"Parar resposta\" — substitui MessageBar durante thinking (F1) */\nfunction StopResponseButton({\n onClick,\n label = \"Parar resposta\",\n}: {\n onClick?: () => void\n label?: string\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className=\"w-full flex items-center justify-center gap-2 h-12 rounded-2xl bg-muted text-neutral-foreground hover:bg-muted/80 transition-colors focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n aria-label={label}\n >\n <Square className=\"size-4\" fill=\"currentColor\" strokeWidth={0} />\n <span className=\"text-sm font-medium\">{label}</span>\n </button>\n )\n}\n\n/** Botao flutuante \"↓ Nova mensagem\" — aparece quando ha msg fora do viewport */\nfunction ScrollToBottomButton({\n onClick,\n hasNewMessage,\n}: {\n onClick?: () => void\n hasNewMessage: boolean\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className=\"absolute bottom-2 left-1/2 -translate-x-1/2 z-10 inline-flex items-center justify-center size-10 rounded-full bg-background border border-border shadow-md hover:bg-muted/50 transition-all focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n aria-label={hasNewMessage ? \"Nova mensagem — ir para o fim\" : \"Ir para o fim\"}\n >\n <ChevronDown className=\"size-5 text-neutral-foreground\" />\n {hasNewMessage && (\n <span\n className=\"absolute top-1 right-1 size-2 rounded-full bg-primary theme-brand\"\n aria-hidden=\"true\"\n />\n )}\n </button>\n )\n}\n\n/** Bubble wrapper com animacao de entrada (E1: fade-in + slide-up 8px, 200ms) */\nfunction MessageBubbleAnimated({\n children,\n enableAnimation,\n}: {\n children: React.ReactNode\n enableAnimation: boolean\n}) {\n return (\n <div\n className={cn(\n \"transition-[opacity,transform] duration-200 ease-out\",\n enableAnimation ? \"animate-thread-msg-enter\" : \"\"\n )}\n >\n {children}\n </div>\n )\n}\n\n/* ─── Bottom slot: gerencia animacao sequencial entre MessageBar / StopButton / hidden ─── */\n\ntype BottomSlotKind = \"messagebar\" | \"stop\" | \"hidden\"\nconst LEAVE_DURATION_MS = 150\n\n/**\n * Slot que alterna entre 3 estados com animacao sequencial (sem cross-fade):\n * o conteudo atual anima saida (`thread-msg-leave`, 150ms), troca o conteudo,\n * e o novo anima entrada (`thread-msg-enter`, 200ms). Similar ao\n * AnimatePresence mode=\"wait\" do Framer Motion, mas em CSS puro.\n */\nfunction BottomSlot({\n kind,\n messageBar,\n stopButton,\n}: {\n kind: BottomSlotKind\n messageBar: React.ReactNode\n stopButton: React.ReactNode\n}) {\n const [rendered, setRendered] = React.useState<BottomSlotKind>(kind)\n const [phase, setPhase] = React.useState<\"enter\" | \"leave\">(\"enter\")\n\n React.useEffect(() => {\n if (rendered === kind) return\n setPhase(\"leave\")\n const t = setTimeout(() => {\n setRendered(kind)\n setPhase(\"enter\")\n }, LEAVE_DURATION_MS)\n return () => clearTimeout(t)\n }, [kind, rendered])\n\n return (\n <div className=\"shrink-0 px-4 pb-4 pt-2\">\n {rendered !== \"hidden\" && (\n <div\n key={rendered}\n className={\n phase === \"enter\" ? \"animate-thread-msg-enter\" : \"animate-thread-msg-leave\"\n }\n >\n {rendered === \"messagebar\" ? messageBar : stopButton}\n </div>\n )}\n </div>\n )\n}\n\n/* ─── Component principal ─────────────────────────────────────── */\n\nfunction ChatThread({\n messages,\n state = \"idle\",\n initialThinking = false,\n onSendText,\n onSendAudio,\n onRetryMessage,\n onRegenerateResponse,\n onStopResponse,\n onLoadMore,\n onStartRecording,\n onPauseRecording,\n onResumeRecording,\n onCancelRecording,\n onTogglePlay,\n onSeekPlayback,\n recordingStream,\n recordingDuration,\n isPlaying,\n playbackProgress,\n offline = false,\n rateLimitedUntil = null,\n rateLimitBannerText,\n quotaExhausted = false,\n quotaExhaustedConfig,\n onRate,\n ratingLabels,\n placeholder,\n maxLength,\n audioOnlyMode = false,\n maxRecordingDuration,\n warnAtSecondsLeft,\n secondsLeftLabel,\n onMaxDurationReached,\n userAvatar,\n userName,\n offlineBannerText = \"Sem conexao. Tentando reconectar...\",\n retryButtonText = \"Tentar novamente\",\n regenerateButtonText = \"Regenerar resposta\",\n stopResponseText = \"Parar resposta\",\n defaultAiErrorText = \"Desculpe, nao consegui responder.\",\n className,\n ...props\n}: ChatThreadProps) {\n /* ─── State derivado ─── */\n const rateLimitSecondsLeft = useCountdown(rateLimitedUntil)\n const isRateLimited = rateLimitSecondsLeft > 0\n const isThinking =\n state === \"thinking\" || (initialThinking && messages.length === 0)\n const isDisabled = offline || isRateLimited || quotaExhausted\n\n /* ─── Refs e scroll inteligente (D1) ─── */\n const scrollRef = React.useRef<HTMLDivElement>(null)\n const [isAtBottom, setIsAtBottom] = React.useState(true)\n const [hasNewBelow, setHasNewBelow] = React.useState(false)\n const prevMessageCountRef = React.useRef(messages.length)\n const loadMoreInFlightRef = React.useRef(false)\n const [isLoadingMore, setIsLoadingMore] = React.useState(false)\n // Marca de quando o componente montou — usada para suprimir animacao das mensagens iniciais\n const mountedRef = React.useRef(false)\n React.useEffect(() => {\n mountedRef.current = true\n }, [])\n\n const checkScrollPosition = React.useCallback(() => {\n const el = scrollRef.current\n if (!el) return\n const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight\n const atBottom = distanceFromBottom <= SCROLL_BOTTOM_THRESHOLD\n setIsAtBottom(atBottom)\n if (atBottom) setHasNewBelow(false)\n }, [])\n\n const scrollToBottom = React.useCallback((smooth = true) => {\n const el = scrollRef.current\n if (!el) return\n el.scrollTo({\n top: el.scrollHeight,\n behavior: smooth ? \"smooth\" : \"auto\",\n })\n setHasNewBelow(false)\n }, [])\n\n /* ─── Auto-scroll quando chega nova mensagem ─── */\n React.useEffect(() => {\n const prev = prevMessageCountRef.current\n const curr = messages.length\n prevMessageCountRef.current = curr\n\n if (curr <= prev) return\n\n if (isAtBottom) {\n // Esta no fundo → scroll suave pra nova msg\n requestAnimationFrame(() => scrollToBottom(true))\n } else {\n // Usuario subiu → mostra botao\n setHasNewBelow(true)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [messages.length])\n\n /* ─── Scroll handler (detecta posicao + paginacao) ─── */\n React.useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n const handler = () => {\n checkScrollPosition()\n // Paginacao: se chegou perto do topo e ha onLoadMore, dispara\n if (\n onLoadMore &&\n !loadMoreInFlightRef.current &&\n el.scrollTop <= LOAD_MORE_THRESHOLD\n ) {\n loadMoreInFlightRef.current = true\n setIsLoadingMore(true)\n const prevScrollHeight = el.scrollHeight\n Promise.resolve(onLoadMore())\n .then(() => {\n // Preserva posicao visual apos prepend de mensagens antigas\n requestAnimationFrame(() => {\n const newScrollHeight = el.scrollHeight\n el.scrollTop = newScrollHeight - prevScrollHeight\n })\n })\n .finally(() => {\n loadMoreInFlightRef.current = false\n setIsLoadingMore(false)\n })\n }\n }\n el.addEventListener(\"scroll\", handler, { passive: true })\n // Inicial: garante scroll no fundo\n requestAnimationFrame(() => scrollToBottom(false))\n return () => el.removeEventListener(\"scroll\", handler)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [onLoadMore])\n\n /* ─── Aria-live announce de nova msg da IA (H1) ─── */\n const lastAiMessage = React.useMemo(() => {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].persona === \"ai\") return messages[i]\n }\n return null\n }, [messages])\n\n /* ─── Rating per-message: rastreia IDs cujo rating foi respondido (auto-dismiss imediato) ─── */\n const [ratedMessageIds, setRatedMessageIds] = React.useState<Set<string>>(() => new Set())\n\n // Merge dos labels customizados com defaults PT-BR. Usado pra renderizar E pra montar o callback.\n const mergedRatingLabels: MessageRatingLabels = React.useMemo(\n () => ({ ...DEFAULT_MESSAGE_RATING_LABELS, ...ratingLabels }),\n [ratingLabels]\n )\n\n const handleRateMessage = React.useCallback(\n (messageId: string, value: MessageRatingValue) => {\n // Marca como respondido IMEDIATAMENTE — re-render remove o rating do DOM antes\n // do paint visual de \"selected\", entao o usuario nao ve o estado intermediario.\n setRatedMessageIds((prev) => {\n const next = new Set(prev)\n next.add(messageId)\n return next\n })\n // Dispara callback com label completo (emoji/title/subtitle).\n onRate?.(messageId, value, mergedRatingLabels[value])\n },\n [onRate, mergedRatingLabels]\n )\n\n /* ─── MessageBar state interno (state machine de gravacao) ─── */\n const baseMessageBarState: MessageBarState = audioOnlyMode ? \"audio-only\" : \"default\"\n const [internalRecordingState, setInternalRecordingState] = React.useState<\n \"idle\" | \"recording\" | \"paused\"\n >(\"idle\")\n\n // Reset pra idle quando muda audioOnlyMode e nao esta gravando\n React.useEffect(() => {\n if (internalRecordingState === \"idle\") return\n // mantem o state se gravacao em andamento\n }, [audioOnlyMode, internalRecordingState])\n\n // State final do MessageBar — combina base, recording state e disabled\n const messageBarState: MessageBarState = isDisabled\n ? \"disabled\"\n : internalRecordingState === \"recording\"\n ? \"recording\"\n : internalRecordingState === \"paused\"\n ? \"paused\"\n : baseMessageBarState\n\n /* Wrappers dos callbacks: atualizam state interno + chamam consumer */\n const handleStartRecordingInternal = React.useCallback(() => {\n setInternalRecordingState(\"recording\")\n onStartRecording?.()\n }, [onStartRecording])\n\n const handlePauseRecordingInternal = React.useCallback(() => {\n setInternalRecordingState(\"paused\")\n onPauseRecording?.()\n }, [onPauseRecording])\n\n const handleResumeRecordingInternal = React.useCallback(() => {\n setInternalRecordingState(\"recording\")\n onResumeRecording?.()\n }, [onResumeRecording])\n\n const handleCancelRecordingInternal = React.useCallback(() => {\n setInternalRecordingState(\"idle\")\n onCancelRecording?.()\n }, [onCancelRecording])\n\n const handleSendAudioInternal = React.useCallback(() => {\n setInternalRecordingState(\"idle\")\n onSendAudio?.()\n }, [onSendAudio])\n\n /* Auto-pause ao atingir limite de duracao. Se consumer passou onMaxDurationReached,\n delega controle pra ele (sem auto-pause). */\n const handleMaxDurationInternal = React.useCallback(() => {\n if (onMaxDurationReached) {\n onMaxDurationReached()\n } else {\n handlePauseRecordingInternal()\n }\n }, [onMaxDurationReached, handlePauseRecordingInternal])\n\n /* Input controlled — sem isso o MessageBar fica \"engessado\" em value=\"\"\n (o consumer recebe o texto via onSendText, mas nao precisa controlar o input). */\n const [inputValue, setInputValue] = React.useState(\"\")\n const handleSendTextInternal = React.useCallback(\n (text: string) => {\n setInputValue(\"\")\n onSendText?.(text)\n },\n [onSendText]\n )\n\n /* ─── Render ──────────────────────────────────────── */\n return (\n <div\n data-slot=\"chat-thread\"\n className={cn(\"relative flex flex-col h-full bg-background\", className)}\n {...props}\n >\n {/* Banners de erro (topo) */}\n {offline && (\n <ChatThreadBanner variant=\"offline\">\n <span aria-hidden=\"true\">⚠</span>\n <span>{offlineBannerText}</span>\n </ChatThreadBanner>\n )}\n {isRateLimited && (\n <ChatThreadBanner variant=\"rate-limit\">\n <span aria-hidden=\"true\">⏱</span>\n <span>\n {rateLimitBannerText\n ? rateLimitBannerText(rateLimitSecondsLeft)\n : `Aguarde ${rateLimitSecondsLeft}s antes de enviar nova mensagem`}\n </span>\n </ChatThreadBanner>\n )}\n\n {/* Thread scrollavel */}\n <div\n ref={scrollRef}\n className=\"flex-1 overflow-y-auto px-4 py-4 space-y-4\"\n role=\"log\"\n aria-live=\"polite\"\n aria-atomic=\"false\"\n >\n {/* Spinner de paginacao no topo (G2) */}\n {isLoadingMore && (\n <div className=\"flex justify-center py-2\" aria-label=\"Carregando mensagens anteriores\">\n <span className=\"inline-block size-5 border-2 border-muted-foreground/30 border-t-muted-foreground rounded-full animate-spin\" />\n </div>\n )}\n\n {messages.map((msg, idx) => {\n // Suprime animacao das mensagens que ja existiam no mount inicial\n const enableAnim =\n mountedRef.current && idx >= prevMessageCountRef.current - 1\n\n const isFailedUser = msg.persona === \"user\" && msg.status === \"failed\"\n const isFailedAi = msg.persona === \"ai\" && msg.status === \"failed\"\n const isPendingUser = msg.persona === \"user\" && msg.status === \"pending\"\n\n return (\n <MessageBubbleAnimated key={msg.id} enableAnimation={enableAnim}>\n <ChatMessage\n persona={msg.persona}\n text={msg.text}\n audioSrc={msg.audioSrc}\n avatar={msg.persona === \"user\" ? userAvatar : undefined}\n author={msg.persona === \"user\" ? userName : undefined}\n // Indicador de pending: typing dots no lugar do conteudo normal e visual sutil\n loading={isPendingUser && !msg.text && !msg.audioSrc}\n className={cn(\n isPendingUser && \"opacity-80\",\n isFailedUser && \"[&_[data-slot=chat-message-bubble]]:border-2 [&_[data-slot=chat-message-bubble]]:border-destructive\"\n )}\n />\n {/* Retry inline pra msg do user que falhou (B2) */}\n {isFailedUser && (\n <div className=\"mt-2 flex justify-end\">\n <button\n type=\"button\"\n onClick={() => onRetryMessage?.(msg.id)}\n className=\"inline-flex items-center gap-1 text-xs text-destructive hover:underline focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 rounded\"\n >\n ↻ {retryButtonText}\n </button>\n </div>\n )}\n {/* Bubble de erro pra resposta da IA (B3) */}\n {isFailedAi && (\n /* Alinhado com a bubble da IA: offset = badge 32px + gap 12px = 44px (desktop).\n Mobile sem offset porque o badge fica oculto no ChatMessage AI. */\n <div className=\"mt-2 sm:pl-11 flex flex-col items-start gap-2\">\n <div className=\"text-sm text-destructive\">\n {msg.errorText || defaultAiErrorText}\n </div>\n <button\n type=\"button\"\n onClick={onRegenerateResponse}\n className=\"inline-flex items-center gap-1 text-xs text-primary hover:underline focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 rounded\"\n >\n ↻ {regenerateButtonText}\n </button>\n </div>\n )}\n {/* MessageRating inline na mensagem da IA que pede avaliacao (J).\n Alinhado com a bubble da IA (offset 44px desktop, 0 mobile).\n value={null} mantem o componente sem estado \"selecionado\" visual —\n ao clicar, o ChatThread marca como rated imediatamente (re-render remove\n do DOM antes do paint), e o consumer recebe (messageId, value, label). */}\n {msg.persona === \"ai\" && msg.requestRating && !ratedMessageIds.has(msg.id) && (\n <div className=\"mt-3 sm:pl-11 animate-thread-msg-enter\">\n <MessageRating\n value={null}\n labels={ratingLabels}\n onChange={(value) => handleRateMessage(msg.id, value)}\n />\n </div>\n )}\n </MessageBubbleAnimated>\n )\n })}\n\n {/* Typing indicator da IA durante thinking */}\n {isThinking && (\n <MessageBubbleAnimated enableAnimation>\n <ChatMessage persona=\"ai\" loading />\n </MessageBubbleAnimated>\n )}\n\n {/* Aria-live oculto pra anunciar ultima msg da IA */}\n <div className=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\">\n {lastAiMessage?.text || \"\"}\n </div>\n </div>\n\n {/* Scroll-to-bottom button (D1) */}\n {!isAtBottom && (\n <ScrollToBottomButton\n onClick={() => scrollToBottom(true)}\n hasNewMessage={hasNewBelow}\n />\n )}\n\n {/* Bottom area: MessageBar / StopResponse / hidden.\n O botao \"Parar resposta\" so aparece durante thinking E quando o consumer passa\n `onStopResponse` — quem nao implementa cancelamento ve o MessageBar sumir com\n animacao de saida e voltar com animacao de entrada quando a IA termina. */}\n <BottomSlot\n kind={\n isThinking\n ? onStopResponse\n ? \"stop\"\n : \"hidden\"\n : \"messagebar\"\n }\n messageBar={\n <MessageBar\n state={messageBarState}\n value={inputValue}\n onChange={setInputValue}\n onSendText={handleSendTextInternal}\n onSendAudio={handleSendAudioInternal}\n onStartRecording={handleStartRecordingInternal}\n onPauseRecording={handlePauseRecordingInternal}\n onResumeRecording={handleResumeRecordingInternal}\n onCancelRecording={handleCancelRecordingInternal}\n onTogglePlay={onTogglePlay}\n onSeekPlayback={onSeekPlayback}\n recordingStream={recordingStream}\n recordingDuration={recordingDuration}\n isPlaying={isPlaying}\n playbackProgress={playbackProgress}\n placeholder={placeholder}\n maxRecordingDuration={maxRecordingDuration}\n warnAtSecondsLeft={warnAtSecondsLeft}\n secondsLeftLabel={secondsLeftLabel}\n onMaxDurationReached={handleMaxDurationInternal}\n />\n }\n stopButton={\n <StopResponseButton onClick={onStopResponse} label={stopResponseText} />\n }\n />\n\n {/* Quota exhausted modal (C3) */}\n {quotaExhausted && (\n <AlertDialog open>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {quotaExhaustedConfig?.title || \"Limite atingido\"}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {quotaExhaustedConfig?.description ||\n \"Voce atingiu o limite de mensagens do seu plano. Faca upgrade para continuar.\"}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n {quotaExhaustedConfig?.onCancel && (\n <AlertDialogCancel onClick={quotaExhaustedConfig.onCancel}>\n {quotaExhaustedConfig.cancelLabel || \"Cancelar\"}\n </AlertDialogCancel>\n )}\n <AlertDialogAction onClick={quotaExhaustedConfig?.onCta}>\n {quotaExhaustedConfig?.ctaLabel || \"Fazer upgrade\"}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )}\n </div>\n )\n}\n\nexport { ChatThread }\n"]}