@adatechnology/conversations-ui 0.1.0-rc.12 → 0.1.0-rc.14

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/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import react__default, { ReactNode, CSSProperties, FormEvent } from 'react';
3
- import { y as MessagePayload, A as ResolveMediaUrl, v as InteractiveSelection, t as InteractivePayload, p as ConversationsFeatures, m as ConversationSummary, h as ConversationChannel, L as ListConversationsParams, o as ConversationsApi, S as SSEProvider, w as ListDocumentsParams, i as ConversationDocument, n as ConversationTemplate } from './types-B5C1DLu1.js';
4
- export { C as CHANNEL_CAPABILITIES, a as CHANNEL_FILTER_ALL, b as CONVERSATION_CHANNEL, c as ChannelCapabilities, d as ChannelFilter, e as ChannelFilterOption, f as CompanyDocument, g as CompanyDocumentPage, j as ConversationDocumentPage, k as ConversationEventSource, l as ConversationPage, q as ConversationsTheme, r as ConversationsUIConfig, D as DEFAULT_CONVERSATION_CHANNEL, F as FormatContactHandleParams, H as HANDLE_KIND, s as HandleKind, I as InteractiveOption, u as InteractiveSection, M as MediaRenderer, x as MediaRendererProps, R as REOPEN_MECHANISM, z as ReopenMechanism, B as capabilitiesOf, E as channelFiltersFor, G as contactFlag, J as formatContactHandle } from './types-B5C1DLu1.js';
3
+ import { G as MessagePayload, K as ResolveMediaUrl, z as InteractiveSelection, x as InteractivePayload, r as ConversationsFeatures, o as ConversationSummary, j as ConversationChannel, L as ListConversationsParams, q as ConversationsApi, S as SSEProvider, B as ListDocumentsParams, k as ConversationDocument, p as ConversationTemplate } from './types-O7kMP1Yn.js';
4
+ export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, C as CHANNEL_CAPABILITIES, c as CHANNEL_FILTER_ALL, d as CONVERSATION_CHANNEL, e as ChannelCapabilities, f as ChannelFilter, g as ChannelFilterOption, h as CompanyDocument, i as CompanyDocumentPage, l as ConversationDocumentPage, m as ConversationEventSource, n as ConversationPage, s as ConversationsTheme, t as ConversationsUIConfig, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, u as DEFAULT_CONVERSATION_CHANNEL, v as DEFAULT_MAX_RECORDING_MILLISECONDS, F as FormatContactHandleParams, H as HANDLE_KIND, w as HandleKind, I as InteractiveOption, y as InteractiveSection, M as MediaRenderer, E as MediaRendererProps, R as REOPEN_MECHANISM, J as ReopenMechanism, N as capabilitiesOf, O as channelFiltersFor, P as contactFlag, Q as formatContactHandle } from './types-O7kMP1Yn.js';
5
5
 
6
6
  interface MessageBubbleProps {
7
7
  message: MessagePayload;
package/dist/index.js CHANGED
@@ -1,15 +1,18 @@
1
1
  import {
2
2
  AudioPlayer,
3
+ AudioRecorderButton,
3
4
  ConversationDocumentsPanel,
4
5
  ConversationLocalesProvider,
5
6
  ConversationWallpaper,
6
7
  ConversationsProvider,
7
8
  DEFAULT_ACCEPTED_FILE_TYPES,
9
+ DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
8
10
  DEFAULT_CONVERSATION_DOCUMENTS_LABELS,
9
11
  DEFAULT_DOCUMENTS_LIBRARY_LABELS,
10
12
  DEFAULT_EMOJI_PICKER_LABELS,
11
13
  DEFAULT_INTERACTIVE_MESSAGE_LABELS,
12
14
  DEFAULT_LIGHTBOX_LABELS,
15
+ DEFAULT_MAX_RECORDING_MILLISECONDS,
13
16
  DEFAULT_MESSAGE_COMPOSER_LABELS,
14
17
  DOCUMENT_SOURCE_FILTER,
15
18
  DateDivider,
@@ -41,7 +44,7 @@ import {
41
44
  useConversationDocuments,
42
45
  useConversationLocales,
43
46
  useConversations
44
- } from "./chunk-3RKFU46R.js";
47
+ } from "./chunk-SOWV4264.js";
45
48
  import {
46
49
  htmlToWA,
47
50
  parseWhatsAppFormatting,
@@ -2271,6 +2274,7 @@ function useInboxActions() {
2271
2274
  }
2272
2275
  export {
2273
2276
  AudioPlayer,
2277
+ AudioRecorderButton,
2274
2278
  Avatar,
2275
2279
  CHANNEL_BRAND_COLOR,
2276
2280
  CHANNEL_CAPABILITIES,
@@ -2287,6 +2291,7 @@ export {
2287
2291
  ConversationWallpaper,
2288
2292
  ConversationsProvider,
2289
2293
  DEFAULT_ACCEPTED_FILE_TYPES,
2294
+ DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
2290
2295
  DEFAULT_AVATAR_LABELS,
2291
2296
  DEFAULT_CONVERSATION_CHANNEL,
2292
2297
  DEFAULT_CONVERSATION_CONTEXT_LABELS,
@@ -2297,6 +2302,7 @@ export {
2297
2302
  DEFAULT_EMOJI_PICKER_LABELS,
2298
2303
  DEFAULT_INTERACTIVE_MESSAGE_LABELS,
2299
2304
  DEFAULT_LIGHTBOX_LABELS,
2305
+ DEFAULT_MAX_RECORDING_MILLISECONDS,
2300
2306
  DEFAULT_MESSAGE_COMPOSER_LABELS,
2301
2307
  DEFAULT_RICH_COMPOSER_TOOLTIPS,
2302
2308
  DEFAULT_TEMPLATES_SETTINGS_LABELS,
@@ -1,4 +1,5 @@
1
- import { y as MessagePayload, m as ConversationSummary, k as ConversationEventSource, o as ConversationsApi, L as ListConversationsParams, l as ConversationPage, S as SSEProvider, i as ConversationDocument, A as ResolveMediaUrl } from '../types-B5C1DLu1.js';
1
+ import { G as MessagePayload, o as ConversationSummary, m as ConversationEventSource, q as ConversationsApi, L as ListConversationsParams, n as ConversationPage, S as SSEProvider, k as ConversationDocument, K as ResolveMediaUrl } from '../types-O7kMP1Yn.js';
2
+ export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS } from '../types-O7kMP1Yn.js';
2
3
  import * as react from 'react';
3
4
  import { InteractiveReplyOption, InboundMediaType } from '@adatechnology/meta-whatsapp-contracts/testing';
4
5
 
@@ -205,41 +206,6 @@ type PreviewUploadedMedia = {
205
206
  declare function mediaTypeOf(mimeType: string): SendPreviewMediaParams['mediaType'];
206
207
  declare function ConversationPreview({ client, sse, conversationId, loadMessages, placeholder, pollIntervalMs, uploadMedia, }: ConversationPreviewProps): react.JSX.Element;
207
208
 
208
- /**
209
- * Gravação de áudio no simulador, pelo microfone do próprio navegador.
210
- *
211
- * Existe porque áudio é o formato que mais chega de cliente real e o que mais quebra fluxo: sem
212
- * poder gravar aqui, testar o caminho de transcrição exigia mandar mensagem do celular de alguém.
213
- *
214
- * O arquivo gravado sai daqui como `File` e segue exatamente o mesmo caminho de um anexo — quem
215
- * hospeda e devolve o `mediaId` é o host, via `uploadMedia`.
216
- */
217
- interface AudioRecorderButtonLabels {
218
- start: string;
219
- stop: string;
220
- unsupported: string;
221
- denied: string;
222
- }
223
- declare const DEFAULT_AUDIO_RECORDER_BUTTON_LABELS: AudioRecorderButtonLabels;
224
- interface AudioRecorderButtonProps {
225
- onRecorded: (file: File) => void | Promise<void>;
226
- onFailure?: (message: string) => void;
227
- /**
228
- * Avisa quando a gravação começa e termina. O botão é um interruptor — o segundo toque é que
229
- * envia — e sem um aviso fora dele o operador grava, não vê nada acontecer e desiste achando
230
- * que o microfone está quebrado.
231
- */
232
- onRecordingChange?: (isRecording: boolean) => void;
233
- /**
234
- * Teto de duração da gravação, em milissegundos. Passado o tempo, o gravador para e envia o que
235
- * tem. Produto com limite próprio sobrescreve.
236
- */
237
- maxDurationMilliseconds?: number;
238
- labels?: Partial<AudioRecorderButtonLabels>;
239
- disabled?: boolean;
240
- }
241
- declare function AudioRecorderButton({ onRecorded, onFailure, onRecordingChange, maxDurationMilliseconds, labels, disabled, }: AudioRecorderButtonProps): react.JSX.Element;
242
-
243
209
  /**
244
210
  * Roteiro que mantém o preview vivo: sem tráfego chegando, a inbox é uma tela estática e as
245
211
  * transições que o atendente precisa testar (fila de espera enchendo, handoff, devolução ao bot)
@@ -338,4 +304,4 @@ type MediaTypesPreviewProps = {
338
304
  };
339
305
  declare function MediaTypesPreview({ conversationId, className, }: MediaTypesPreviewProps): react.JSX.Element;
340
306
 
341
- export { type AppendMessageParams, AudioRecorderButton, type AudioRecorderButtonLabels, type AudioRecorderButtonProps, ConversationPreview, type ConversationPreviewProps, type CreateMockConversationsApiParams, type CreateMockSSEProviderParams, type CreatePreviewStoreParams, type CreatePreviewWebhookClientParams, DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, DEFAULT_PREVIEW_SCRIPT, GLOBAL_CHANNEL, type ListConversationsFilters, MEDIA_TYPES_CONVERSATION_ID, MediaTypesPreview, type MediaTypesPreviewProps, type MockEventSource, PREVIEW_CONVERSATIONS, PREVIEW_DOCUMENTS, PREVIEW_FILE_SAMPLES, PREVIEW_MESSAGES, type PreviewEmission, PreviewInProductionError, type PreviewScriptStep, type PreviewStore, type PreviewStoreListener, type PreviewUploadedMedia, type PreviewWebhookClient, PreviewWebhookRejectedError, type SendPreviewMediaParams, type SetModeParams, type StartPreviewScriptParams, assertPreviewEnvironment, conversationChannel, createMockConversationsApi, createMockEventSource, createMockSSEProvider, createPreviewMediaResolver, createPreviewStore, createPreviewWebhookClient, mediaTypeOf, previewFileBase64, previewFileUrl, resolvePreviewFileSample, signPreviewPayload, startPreviewScript };
307
+ export { type AppendMessageParams, ConversationPreview, type ConversationPreviewProps, type CreateMockConversationsApiParams, type CreateMockSSEProviderParams, type CreatePreviewStoreParams, type CreatePreviewWebhookClientParams, DEFAULT_PREVIEW_SCRIPT, GLOBAL_CHANNEL, type ListConversationsFilters, MEDIA_TYPES_CONVERSATION_ID, MediaTypesPreview, type MediaTypesPreviewProps, type MockEventSource, PREVIEW_CONVERSATIONS, PREVIEW_DOCUMENTS, PREVIEW_FILE_SAMPLES, PREVIEW_MESSAGES, type PreviewEmission, PreviewInProductionError, type PreviewScriptStep, type PreviewStore, type PreviewStoreListener, type PreviewUploadedMedia, type PreviewWebhookClient, PreviewWebhookRejectedError, type SendPreviewMediaParams, type SetModeParams, type StartPreviewScriptParams, assertPreviewEnvironment, conversationChannel, createMockConversationsApi, createMockEventSource, createMockSSEProvider, createPreviewMediaResolver, createPreviewStore, createPreviewWebhookClient, mediaTypeOf, previewFileBase64, previewFileUrl, resolvePreviewFileSample, signPreviewPayload, startPreviewScript };
@@ -1,12 +1,14 @@
1
1
  import {
2
+ AudioRecorderButton,
2
3
  ConversationDocumentsPanel,
3
4
  ConversationWallpaper,
4
5
  ConversationsProvider,
6
+ DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
5
7
  DateDivider,
6
8
  DocumentsLibrary,
7
9
  MessageBubble,
8
10
  MessageComposer
9
- } from "../chunk-3RKFU46R.js";
11
+ } from "../chunk-SOWV4264.js";
10
12
  import "../chunk-2AYDBWNE.js";
11
13
 
12
14
  // src/preview/previewStore.ts
@@ -845,98 +847,8 @@ function createMockSSEProvider(params) {
845
847
  }
846
848
 
847
849
  // src/preview/ConversationPreview.tsx
848
- import { useCallback as useCallback2, useEffect, useMemo, useRef as useRef2, useState as useState2 } from "react";
849
-
850
- // src/preview/AudioRecorderButton.tsx
851
- import { useCallback, useRef, useState } from "react";
850
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
852
851
  import { jsx, jsxs } from "react/jsx-runtime";
853
- var DEFAULT_AUDIO_RECORDER_BUTTON_LABELS = {
854
- start: "Gravar \xE1udio",
855
- stop: "Parar grava\xE7\xE3o",
856
- unsupported: "Este navegador n\xE3o grava \xE1udio.",
857
- denied: "Sem permiss\xE3o para usar o microfone."
858
- };
859
- var DEFAULT_MAX_RECORDING_MILLISECONDS = 5 * 60 * 1e3;
860
- var RECORDING_FORMATS = [
861
- { mimeType: "audio/ogg;codecs=opus", uploadMimeType: "audio/ogg", extension: "ogg" },
862
- { mimeType: "audio/mp4", uploadMimeType: "audio/mp4", extension: "m4a" },
863
- { mimeType: "audio/webm", uploadMimeType: "audio/webm", extension: "webm" }
864
- ];
865
- function resolveRecordingFormat() {
866
- if (typeof MediaRecorder === "undefined") return void 0;
867
- if (typeof MediaRecorder.isTypeSupported !== "function") return RECORDING_FORMATS[0];
868
- return RECORDING_FORMATS.find((format) => MediaRecorder.isTypeSupported(format.mimeType));
869
- }
870
- function AudioRecorderButton({
871
- onRecorded,
872
- onFailure,
873
- onRecordingChange,
874
- maxDurationMilliseconds = DEFAULT_MAX_RECORDING_MILLISECONDS,
875
- labels,
876
- disabled
877
- }) {
878
- const startLabel = labels?.start ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.start;
879
- const stopLabel = labels?.stop ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.stop;
880
- const [isRecording, setIsRecording] = useState(false);
881
- const recorderRef = useRef(null);
882
- const autoStopRef = useRef(void 0);
883
- const stop = useCallback(() => {
884
- recorderRef.current?.stop();
885
- }, []);
886
- const start = useCallback(async () => {
887
- const format = resolveRecordingFormat();
888
- if (!format || !navigator.mediaDevices?.getUserMedia) {
889
- onFailure?.(labels?.unsupported ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.unsupported);
890
- return;
891
- }
892
- try {
893
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
894
- const recorder = new MediaRecorder(stream, { mimeType: format.mimeType });
895
- const chunks = [];
896
- recorder.addEventListener("dataavailable", (event) => {
897
- if (event.data.size > 0) chunks.push(event.data);
898
- });
899
- recorder.addEventListener("stop", () => {
900
- stream.getTracks().forEach((track) => track.stop());
901
- clearTimeout(autoStopRef.current);
902
- setIsRecording(false);
903
- onRecordingChange?.(false);
904
- recorderRef.current = null;
905
- const blob = new Blob(chunks, { type: format.uploadMimeType });
906
- void onRecorded(
907
- new File([blob], `audio-${Date.now()}.${format.extension}`, { type: format.uploadMimeType })
908
- );
909
- });
910
- recorderRef.current = recorder;
911
- recorder.start();
912
- autoStopRef.current = setTimeout(() => recorder.stop(), maxDurationMilliseconds);
913
- setIsRecording(true);
914
- onRecordingChange?.(true);
915
- } catch {
916
- onFailure?.(labels?.denied ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.denied);
917
- }
918
- }, [labels?.denied, labels?.unsupported, maxDurationMilliseconds, onFailure, onRecorded, onRecordingChange]);
919
- return /* @__PURE__ */ jsx(
920
- "button",
921
- {
922
- type: "button",
923
- disabled,
924
- onClick: () => isRecording ? stop() : void start(),
925
- title: isRecording ? stopLabel : startLabel,
926
- "aria-label": isRecording ? stopLabel : startLabel,
927
- "aria-pressed": isRecording,
928
- className: `flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full transition-colors ${isRecording ? "animate-pulse bg-red-500 text-white ring-4 ring-red-500/30 hover:bg-red-600" : "text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700"}`,
929
- children: isRecording ? /* @__PURE__ */ jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) }) : /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
930
- /* @__PURE__ */ jsx("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z" }),
931
- /* @__PURE__ */ jsx("path", { d: "M19 11a7 7 0 0 1-14 0" }),
932
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "18", x2: "12", y2: "22" })
933
- ] })
934
- }
935
- );
936
- }
937
-
938
- // src/preview/ConversationPreview.tsx
939
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
940
852
  function mediaTypeOf(mimeType) {
941
853
  if (mimeType.startsWith("image/")) return "image";
942
854
  if (mimeType.startsWith("video/")) return "video";
@@ -983,15 +895,15 @@ function ConversationPreview({
983
895
  pollIntervalMs,
984
896
  uploadMedia
985
897
  }) {
986
- const [messages, setMessages] = useState2([]);
987
- const [failure, setFailure] = useState2(void 0);
988
- const [loadFailure, setLoadFailure] = useState2(void 0);
989
- const [isRecording, setIsRecording] = useState2(false);
990
- const [pendingLocal, setPendingLocal] = useState2([]);
991
- const loadMessagesRef = useRef2(loadMessages);
992
- const bottomRef = useRef2(null);
898
+ const [messages, setMessages] = useState([]);
899
+ const [failure, setFailure] = useState(void 0);
900
+ const [loadFailure, setLoadFailure] = useState(void 0);
901
+ const [isRecording, setIsRecording] = useState(false);
902
+ const [pendingLocal, setPendingLocal] = useState([]);
903
+ const loadMessagesRef = useRef(loadMessages);
904
+ const bottomRef = useRef(null);
993
905
  loadMessagesRef.current = loadMessages;
994
- const refresh = useCallback2(async () => {
906
+ const refresh = useCallback(async () => {
995
907
  try {
996
908
  const loaded = await loadMessagesRef.current(conversationId);
997
909
  setMessages(loaded);
@@ -1084,11 +996,11 @@ function ConversationPreview({
1084
996
  setFailure(error instanceof Error ? error.message : "Falha ao enviar o arquivo.");
1085
997
  }
1086
998
  }
1087
- return /* @__PURE__ */ jsxs2("div", { className: "flex h-full min-h-0 flex-col", children: [
1088
- /* @__PURE__ */ jsxs2(ConversationWallpaper, { className: "flex-1 min-h-0 overflow-y-auto px-4 py-3", children: [
1089
- rendered.map(({ message, isFirstInGroup, showDateDivider }) => /* @__PURE__ */ jsxs2("div", { children: [
1090
- showDateDivider ? /* @__PURE__ */ jsx2(DateDivider, { iso: message.timestamp }) : null,
1091
- /* @__PURE__ */ jsx2(
999
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
1000
+ /* @__PURE__ */ jsxs(ConversationWallpaper, { className: "flex-1 min-h-0 overflow-y-auto px-4 py-3", children: [
1001
+ rendered.map(({ message, isFirstInGroup, showDateDivider }) => /* @__PURE__ */ jsxs("div", { children: [
1002
+ showDateDivider ? /* @__PURE__ */ jsx(DateDivider, { iso: message.timestamp }) : null,
1003
+ /* @__PURE__ */ jsx(
1092
1004
  MessageBubble,
1093
1005
  {
1094
1006
  message,
@@ -1098,17 +1010,17 @@ function ConversationPreview({
1098
1010
  }
1099
1011
  )
1100
1012
  ] }, message.id)),
1101
- /* @__PURE__ */ jsx2("div", { ref: bottomRef })
1013
+ /* @__PURE__ */ jsx("div", { ref: bottomRef })
1102
1014
  ] }),
1103
- failure ? /* @__PURE__ */ jsx2("p", { role: "alert", className: "px-4 py-2 text-sm text-red-600 dark:text-red-400", children: failure }) : null,
1104
- loadFailure ? /* @__PURE__ */ jsx2("p", { role: "status", className: "px-4 py-2 text-sm text-amber-700 dark:text-amber-400", children: loadFailure }) : null,
1105
- /* @__PURE__ */ jsx2(
1015
+ failure ? /* @__PURE__ */ jsx("p", { role: "alert", className: "px-4 py-2 text-sm text-red-600 dark:text-red-400", children: failure }) : null,
1016
+ loadFailure ? /* @__PURE__ */ jsx("p", { role: "status", className: "px-4 py-2 text-sm text-amber-700 dark:text-amber-400", children: loadFailure }) : null,
1017
+ /* @__PURE__ */ jsx(
1106
1018
  MessageComposer,
1107
1019
  {
1108
1020
  onSend: (text) => void handleSend(text),
1109
1021
  onAttach: uploadMedia ? (file) => void handleAttach(file) : void 0,
1110
- placeholder: isRecording ? "Gravando\u2026 toque no quadrado para enviar" : placeholder ?? "Escreva como o cliente\u2026",
1111
- idleAction: uploadMedia ? /* @__PURE__ */ jsx2(
1022
+ placeholder: isRecording ? "Gravando\u2026 toque no quadrado para ouvir" : placeholder ?? "Escreva como o cliente\u2026",
1023
+ idleAction: uploadMedia ? /* @__PURE__ */ jsx(
1112
1024
  AudioRecorderButton,
1113
1025
  {
1114
1026
  onRecorded: (file) => void handleAttach(file),
@@ -1218,7 +1130,7 @@ function startPreviewScript(params) {
1218
1130
 
1219
1131
  // src/preview/MediaTypesPreview.tsx
1220
1132
  import { useMemo as useMemo2 } from "react";
1221
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1133
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1222
1134
  var MEDIA_TYPES_CONVERSATION_ID = "5511944443333";
1223
1135
  function MediaTypesPreview({
1224
1136
  conversationId = MEDIA_TYPES_CONVERSATION_ID,
@@ -1233,26 +1145,26 @@ function MediaTypesPreview({
1233
1145
  const messages = PREVIEW_MESSAGES[conversationId] ?? [];
1234
1146
  const documents = PREVIEW_DOCUMENTS[conversationId] ?? [];
1235
1147
  const mimeTypes = [...new Set(documents.map((document) => document.mimeType))];
1236
- return /* @__PURE__ */ jsx3(ConversationsProvider, { api, sse, children: /* @__PURE__ */ jsxs3("div", { className, children: [
1237
- /* @__PURE__ */ jsxs3("header", { className: "border-b px-4 py-3 dark:border-gray-700", children: [
1238
- /* @__PURE__ */ jsx3("h1", { className: "text-lg font-semibold", children: "Teste manual de m\xEDdia" }),
1239
- /* @__PURE__ */ jsxs3("p", { className: "text-sm text-gray-500", children: [
1148
+ return /* @__PURE__ */ jsx2(ConversationsProvider, { api, sse, children: /* @__PURE__ */ jsxs2("div", { className, children: [
1149
+ /* @__PURE__ */ jsxs2("header", { className: "border-b px-4 py-3 dark:border-gray-700", children: [
1150
+ /* @__PURE__ */ jsx2("h1", { className: "text-lg font-semibold", children: "Teste manual de m\xEDdia" }),
1151
+ /* @__PURE__ */ jsxs2("p", { className: "text-sm text-gray-500", children: [
1240
1152
  documents.length,
1241
1153
  " arquivos, ",
1242
1154
  mimeTypes.length,
1243
1155
  " tipos. Clique no olho para abrir em aba nova e no bot\xE3o da bolha para carregar a m\xEDdia na thread \u2014 \xE9 o que teste automatizado n\xE3o v\xEA."
1244
1156
  ] })
1245
1157
  ] }),
1246
- /* @__PURE__ */ jsxs3("div", { className: "grid gap-4 p-4 lg:grid-cols-2", children: [
1247
- /* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
1248
- /* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Biblioteca da empresa" }),
1249
- /* @__PURE__ */ jsx3(DocumentsLibrary, { perPage: documents.length || 20 })
1158
+ /* @__PURE__ */ jsxs2("div", { className: "grid gap-4 p-4 lg:grid-cols-2", children: [
1159
+ /* @__PURE__ */ jsxs2("section", { className: "space-y-3", children: [
1160
+ /* @__PURE__ */ jsx2("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Biblioteca da empresa" }),
1161
+ /* @__PURE__ */ jsx2(DocumentsLibrary, { perPage: documents.length || 20 })
1250
1162
  ] }),
1251
- /* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
1252
- /* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Painel da conversa" }),
1253
- /* @__PURE__ */ jsx3(ConversationDocumentsPanel, { conversationId, open: true, perPage: documents.length || 20 }),
1254
- /* @__PURE__ */ jsx3("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Bolhas na thread" }),
1255
- /* @__PURE__ */ jsx3(ConversationWallpaper, { className: "max-h-[70vh] overflow-y-auto rounded-lg px-3 py-2", children: messages.map((message, index) => /* @__PURE__ */ jsx3(
1163
+ /* @__PURE__ */ jsxs2("section", { className: "space-y-3", children: [
1164
+ /* @__PURE__ */ jsx2("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Painel da conversa" }),
1165
+ /* @__PURE__ */ jsx2(ConversationDocumentsPanel, { conversationId, open: true, perPage: documents.length || 20 }),
1166
+ /* @__PURE__ */ jsx2("h2", { className: "text-sm font-semibold uppercase tracking-wide text-gray-500", children: "Bolhas na thread" }),
1167
+ /* @__PURE__ */ jsx2(ConversationWallpaper, { className: "max-h-[70vh] overflow-y-auto rounded-lg px-3 py-2", children: messages.map((message, index) => /* @__PURE__ */ jsx2(
1256
1168
  MessageBubble,
1257
1169
  {
1258
1170
  message,
@@ -104,6 +104,57 @@ interface MediaRendererProps {
104
104
  }
105
105
  declare function MediaRenderer({ message, onLightbox, onResolveUrl, className }: MediaRendererProps): react.JSX.Element | null;
106
106
 
107
+ /**
108
+ * Gravação de áudio no simulador, pelo microfone do próprio navegador.
109
+ *
110
+ * Existe porque áudio é o formato que mais chega de cliente real e o que mais quebra fluxo: sem
111
+ * poder gravar aqui, testar o caminho de transcrição exigia mandar mensagem do celular de alguém.
112
+ *
113
+ * O arquivo gravado sai daqui como `File` e segue exatamente o mesmo caminho de um anexo — quem
114
+ * hospeda e devolve o `mediaId` é o host, via `uploadMedia`.
115
+ */
116
+ interface AudioRecorderButtonLabels {
117
+ start: string;
118
+ stop: string;
119
+ unsupported: string;
120
+ denied: string;
121
+ review: string;
122
+ send: string;
123
+ discard: string;
124
+ empty: string;
125
+ }
126
+ declare const DEFAULT_AUDIO_RECORDER_BUTTON_LABELS: AudioRecorderButtonLabels;
127
+ interface AudioRecorderButtonProps {
128
+ onRecorded: (file: File) => void | Promise<void>;
129
+ onFailure?: (message: string) => void;
130
+ /**
131
+ * Avisa quando a gravação começa e termina. O botão é um interruptor — o segundo toque é que
132
+ * envia — e sem um aviso fora dele o operador grava, não vê nada acontecer e desiste achando
133
+ * que o microfone está quebrado.
134
+ */
135
+ onRecordingChange?: (isRecording: boolean) => void;
136
+ /**
137
+ * Abre uma etapa de revisão quando a gravação para: o áudio toca ali mesmo e só sai depois de
138
+ * confirmado. Ligado por padrão — voz é o único anexo que quem envia não viu antes de mandar, e
139
+ * sem ouvir não há como saber se o microfone captou alguma coisa. Desligar volta ao envio direto.
140
+ */
141
+ reviewBeforeSend?: boolean;
142
+ /**
143
+ * Teto de duração da gravação, em milissegundos. Passado o tempo, o gravador para e envia o que
144
+ * tem. Produto com limite próprio sobrescreve.
145
+ */
146
+ maxDurationMilliseconds?: number;
147
+ labels?: Partial<AudioRecorderButtonLabels>;
148
+ disabled?: boolean;
149
+ }
150
+ /**
151
+ * Cinco minutos: com o codec de voz do WhatsApp isso dá menos de 3MB, folgado dentro do teto de
152
+ * 16MB que a Meta impõe a áudio, e é mais do que qualquer recado de cliente. O corte automático
153
+ * existe porque gravação esquecida aberta só se descobre no envio, com o arquivo inteiro perdido.
154
+ */
155
+ declare const DEFAULT_MAX_RECORDING_MILLISECONDS: number;
156
+ declare function AudioRecorderButton({ onRecorded, onFailure, onRecordingChange, reviewBeforeSend, maxDurationMilliseconds, labels, disabled, }: AudioRecorderButtonProps): react.JSX.Element;
157
+
107
158
  /**
108
159
  * Canal de origem da conversa e o que cada um permite.
109
160
  *
@@ -362,4 +413,4 @@ interface ConversationDocument {
362
413
  linkedAt: string;
363
414
  }
364
415
 
365
- export { type ResolveMediaUrl as A, capabilitiesOf as B, CHANNEL_CAPABILITIES as C, DEFAULT_CONVERSATION_CHANNEL as D, channelFiltersFor as E, type FormatContactHandleParams as F, contactFlag as G, HANDLE_KIND as H, type InteractiveOption as I, formatContactHandle as J, type ListConversationsParams as L, MediaRenderer as M, REOPEN_MECHANISM as R, type SSEProvider as S, CHANNEL_FILTER_ALL as a, CONVERSATION_CHANNEL as b, type ChannelCapabilities as c, type ChannelFilter as d, type ChannelFilterOption as e, type CompanyDocument as f, type CompanyDocumentPage as g, type ConversationChannel as h, type ConversationDocument as i, type ConversationDocumentPage as j, type ConversationEventSource as k, type ConversationPage as l, type ConversationSummary as m, type ConversationTemplate as n, type ConversationsApi as o, type ConversationsFeatures as p, type ConversationsTheme as q, type ConversationsUIConfig as r, type HandleKind as s, type InteractivePayload as t, type InteractiveSection as u, type InteractiveSelection as v, type ListDocumentsParams as w, type MediaRendererProps as x, type MessagePayload as y, type ReopenMechanism as z };
416
+ export { AudioRecorderButton as A, type ListDocumentsParams as B, CHANNEL_CAPABILITIES as C, DEFAULT_AUDIO_RECORDER_BUTTON_LABELS as D, type MediaRendererProps as E, type FormatContactHandleParams as F, type MessagePayload as G, HANDLE_KIND as H, type InteractiveOption as I, type ReopenMechanism as J, type ResolveMediaUrl as K, type ListConversationsParams as L, MediaRenderer as M, capabilitiesOf as N, channelFiltersFor as O, contactFlag as P, formatContactHandle as Q, REOPEN_MECHANISM as R, type SSEProvider as S, type AudioRecorderButtonLabels as a, type AudioRecorderButtonProps as b, CHANNEL_FILTER_ALL as c, CONVERSATION_CHANNEL as d, type ChannelCapabilities as e, type ChannelFilter as f, type ChannelFilterOption as g, type CompanyDocument as h, type CompanyDocumentPage as i, type ConversationChannel as j, type ConversationDocument as k, type ConversationDocumentPage as l, type ConversationEventSource as m, type ConversationPage as n, type ConversationSummary as o, type ConversationTemplate as p, type ConversationsApi as q, type ConversationsFeatures as r, type ConversationsTheme as s, type ConversationsUIConfig as t, DEFAULT_CONVERSATION_CHANNEL as u, DEFAULT_MAX_RECORDING_MILLISECONDS as v, type HandleKind as w, type InteractivePayload as x, type InteractiveSection as y, type InteractiveSelection as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adatechnology/conversations-ui",
3
- "version": "0.1.0-rc.12",
3
+ "version": "0.1.0-rc.14",
4
4
  "description": "WhatsApp conversation UI components — parametrizável por endpoint, tema e feature flags",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import { renderToStaticMarkup } from 'react-dom/server'
3
+
4
+ import { AudioRecorderButton, DEFAULT_AUDIO_RECORDER_BUTTON_LABELS } from './AudioRecorderButton'
5
+
6
+ const noop = () => {}
7
+
8
+ describe('AudioRecorderButton', () => {
9
+ it('começa oferecendo gravar, sem painel de revisão à vista', () => {
10
+ const markup = renderToStaticMarkup(<AudioRecorderButton onRecorded={noop} />)
11
+
12
+ expect(markup).toContain(`title="${DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.start}"`)
13
+ expect(markup).not.toContain(`title="${DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.send}"`)
14
+ expect(markup).not.toContain('<audio')
15
+ })
16
+
17
+ it('deixa o host trocar o texto de um rótulo sem perder os outros', () => {
18
+ const markup = renderToStaticMarkup(
19
+ <AudioRecorderButton onRecorded={noop} labels={{ start: 'Gravar recado' }} />,
20
+ )
21
+
22
+ expect(markup).toContain('title="Gravar recado"')
23
+ })
24
+
25
+ it('mantém a caixa de 40px do botão de enviar para a barra não pular', () => {
26
+ const markup = renderToStaticMarkup(<AudioRecorderButton onRecorded={noop} />)
27
+
28
+ expect(markup).toContain('h-10 w-10')
29
+ })
30
+ })