@adatechnology/conversations-ui 0.1.0-rc.1 → 0.1.0-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/{chunk-ZDURDZTM.js → chunk-2AYDBWNE.js} +14 -0
  2. package/dist/chunk-3RKFU46R.js +1778 -0
  3. package/dist/flows/index.js +3 -3
  4. package/dist/index.d.ts +644 -136
  5. package/dist/index.js +1068 -1024
  6. package/dist/preview/index.d.ts +341 -0
  7. package/dist/preview/index.js +1296 -0
  8. package/dist/styles.css +198 -0
  9. package/dist/types-B5C1DLu1.d.ts +365 -0
  10. package/package.json +10 -3
  11. package/src/Avatar.tsx +30 -4
  12. package/src/ChannelIcon.tsx +87 -0
  13. package/src/ConversationContextPanel.tsx +106 -0
  14. package/src/ConversationDocumentsPanel.tsx +425 -0
  15. package/src/ConversationHeader.tsx +257 -0
  16. package/src/ConversationListItem.tsx +54 -7
  17. package/src/ConversationLocalesProvider.tsx +16 -0
  18. package/src/ConversationRow.tsx +137 -0
  19. package/src/DateDivider.tsx +16 -3
  20. package/src/DocumentsLibrary.tsx +322 -0
  21. package/src/EmojiPicker.tsx +69 -55
  22. package/src/FileIcon.test.ts +83 -0
  23. package/src/FileIcon.tsx +88 -11
  24. package/src/InteractiveMessage.test.tsx +41 -0
  25. package/src/InteractiveMessage.tsx +143 -0
  26. package/src/Lightbox.tsx +18 -3
  27. package/src/MediaRenderer.tsx +14 -11
  28. package/src/MessageBubble.tsx +54 -5
  29. package/src/MessageComposer.test.tsx +35 -0
  30. package/src/MessageComposer.tsx +146 -19
  31. package/src/Wallpaper.test.tsx +21 -0
  32. package/src/Wallpaper.tsx +54 -6
  33. package/src/WhatsAppMessageEditor.tsx +28 -4
  34. package/src/WindowExpiredNotice.tsx +57 -0
  35. package/src/conversationChannel.test.ts +53 -0
  36. package/src/conversationChannel.ts +146 -0
  37. package/src/conversationTranscript.test.ts +65 -0
  38. package/src/conversationTranscript.ts +64 -0
  39. package/src/conversationWindow.test.ts +90 -0
  40. package/src/conversationWindow.ts +78 -0
  41. package/src/emojiCatalog.test.ts +35 -0
  42. package/src/emojiCatalog.ts +189 -0
  43. package/src/flows/FlowMapCanvas.tsx +2 -2
  44. package/src/hooks/useConversationActions.ts +56 -0
  45. package/src/hooks/useConversationDocuments.ts +15 -9
  46. package/src/hooks/useConversationList.ts +15 -9
  47. package/src/hooks/useConversationMessages.ts +2 -2
  48. package/src/index.ts +114 -16
  49. package/src/lib/cn.test.ts +29 -0
  50. package/src/lib/cn.ts +15 -0
  51. package/src/lib/createMediaUrlResolver.ts +33 -0
  52. package/src/lib/paginated.test.ts +33 -0
  53. package/src/lib/paginated.ts +26 -0
  54. package/src/lib/phone.ts +34 -0
  55. package/src/preview/AudioRecorderButton.tsx +155 -0
  56. package/src/preview/ConversationPreview.tsx +322 -0
  57. package/src/preview/MediaTypesPreview.tsx +87 -0
  58. package/src/preview/audioRecorderFormat.test.ts +67 -0
  59. package/src/preview/conversationPreviewFailures.test.ts +64 -0
  60. package/src/preview/createMockConversationsApi.ts +271 -0
  61. package/src/preview/createMockSSEProvider.ts +40 -0
  62. package/src/preview/createPreviewWebhookClient.test.ts +105 -0
  63. package/src/preview/createPreviewWebhookClient.ts +126 -0
  64. package/src/preview/index.ts +54 -0
  65. package/src/preview/mediaTypeOf.test.ts +15 -0
  66. package/src/preview/mockDocumentsSearch.test.ts +57 -0
  67. package/src/preview/mockEventSource.ts +53 -0
  68. package/src/preview/preview.test.ts +177 -0
  69. package/src/preview/previewFileSamples.test.ts +151 -0
  70. package/src/preview/previewFileSamples.ts +74 -0
  71. package/src/preview/previewFixtures.ts +440 -0
  72. package/src/preview/previewMediaSource.test.ts +62 -0
  73. package/src/preview/previewMediaSource.ts +91 -0
  74. package/src/preview/previewStore.ts +193 -0
  75. package/src/preview/startPreviewScript.ts +60 -0
  76. package/src/providers/types.ts +163 -11
  77. package/src/quickReply.test.ts +58 -0
  78. package/src/settings/WhatsAppCreateTemplateForm.tsx +3 -1
  79. package/src/settings/WhatsAppTemplatesSettings.tsx +106 -0
  80. package/src/styles.css +136 -0
  81. package/src/types.ts +46 -1
  82. package/src/useDarkMode.ts +26 -0
  83. package/src/useIsNarrow.ts +29 -0
  84. package/src/useWaitingNotifications.ts +74 -29
package/dist/index.d.ts CHANGED
@@ -1,55 +1,7 @@
1
1
  import * as react from 'react';
2
- import react__default, { ReactNode, FormEvent } from 'react';
3
-
4
- interface ConversationsUIConfig {
5
- apiBaseUrl: string;
6
- theme?: ConversationsTheme;
7
- features?: ConversationsFeatures;
8
- }
9
- interface ConversationsTheme {
10
- primaryColor?: string;
11
- backgroundColor?: string;
12
- bubbleSent?: string;
13
- bubbleReceived?: string;
14
- textPrimary?: string;
15
- textSecondary?: string;
16
- }
17
- interface ConversationsFeatures {
18
- audio?: boolean;
19
- documents?: boolean;
20
- emoji?: boolean;
21
- darkMode?: boolean;
22
- }
23
- interface MessagePayload {
24
- id: string;
25
- type: 'text' | 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'template';
26
- content?: string;
27
- caption?: string;
28
- mediaUrl?: string;
29
- base64?: string;
30
- uploadId?: string;
31
- mediaId?: string;
32
- mimeType?: string;
33
- filename?: string;
34
- sizeBytes?: number;
35
- direction: 'inbound' | 'outbound';
36
- sender: 'bot' | 'customer' | 'agent';
37
- timestamp: string;
38
- status?: 'sent' | 'delivered' | 'read' | 'failed';
39
- readAt?: string;
40
- agentName?: string | null;
41
- templateName?: string;
42
- isFirstInGroup?: boolean;
43
- isLastInGroup?: boolean;
44
- }
45
-
46
- type ResolveMediaUrl = (message: MessagePayload) => Promise<string | null>;
47
- interface MediaRendererProps {
48
- message: MessagePayload;
49
- onLightbox: (src: string) => void;
50
- onResolveUrl?: ResolveMediaUrl;
51
- }
52
- declare function MediaRenderer({ message, onLightbox, onResolveUrl }: MediaRendererProps): react.JSX.Element | null;
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';
53
5
 
54
6
  interface MessageBubbleProps {
55
7
  message: MessagePayload;
@@ -59,15 +11,40 @@ interface MessageBubbleProps {
59
11
  isSelecting?: boolean;
60
12
  isSelected?: boolean;
61
13
  onToggleSelect?: () => void;
14
+ /**
15
+ * Como buscar a mídia da mensagem. Ausente, o balão usa o `ConversationsApi` do
16
+ * `ConversationsProvider` — passe apenas para sobrescrever (cache próprio, CDN, proxy do host).
17
+ */
62
18
  onResolveMediaUrl?: ResolveMediaUrl;
19
+ /**
20
+ * Toque numa opção do menu interativo. Ausente, as opções aparecem desabilitadas — que é o certo
21
+ * no histórico da inbox: o operador vê o que foi oferecido, sem responder no lugar do cliente.
22
+ */
23
+ onInteractiveSelect?: (selection: InteractiveSelection) => void;
24
+ className?: string;
25
+ }
26
+ declare function MessageBubble({ message, isMine, senderName, isFirstInGroup, isSelecting, isSelected, onToggleSelect, onResolveMediaUrl, onInteractiveSelect, className, }: MessageBubbleProps): react.JSX.Element;
27
+
28
+ interface InteractiveMessageLabels {
29
+ /** Fallback do rótulo do botão que abre a lista, quando o payload não traz um. */
30
+ openList: string;
31
+ }
32
+ declare const DEFAULT_INTERACTIVE_MESSAGE_LABELS: InteractiveMessageLabels;
33
+ interface InteractiveMessageProps {
34
+ payload: InteractivePayload;
35
+ onSelect?: (selection: InteractiveSelection) => void;
36
+ labels?: Partial<InteractiveMessageLabels>;
37
+ className?: string;
63
38
  }
64
- declare function MessageBubble({ message, isMine, senderName, isFirstInGroup, isSelecting, isSelected, onToggleSelect, onResolveMediaUrl, }: MessageBubbleProps): react.JSX.Element;
39
+ declare function InteractiveMessage({ payload, onSelect, labels, className }: InteractiveMessageProps): react.JSX.Element;
65
40
 
66
41
  interface ConversationWallpaperProps {
67
42
  children?: ReactNode;
68
43
  className?: string;
44
+ /** Ajusta ou substitui o fundo padrão — para produto com identidade visual própria. */
45
+ style?: CSSProperties;
69
46
  }
70
- declare function ConversationWallpaper({ children, className }: ConversationWallpaperProps): react.JSX.Element;
47
+ declare function ConversationWallpaper({ children, className, style }: ConversationWallpaperProps): react.JSX.Element;
71
48
 
72
49
  interface ConversationLocales {
73
50
  bubble: {
@@ -82,6 +59,14 @@ interface ConversationLocales {
82
59
  viewImage: string;
83
60
  listenAudio: string;
84
61
  viewVideo: string;
62
+ moderationFlagged: string;
63
+ mediaLoading: string;
64
+ mediaRetry: string;
65
+ mediaError: string;
66
+ mediaUnavailable: string;
67
+ imageAlt: string;
68
+ untitledDocument: string;
69
+ downloadFile: string;
85
70
  };
86
71
  selection: {
87
72
  select: string;
@@ -107,13 +92,69 @@ interface AudioPlayerProps {
107
92
  }
108
93
  declare function AudioPlayer({ src, isMine }: AudioPlayerProps): react.JSX.Element;
109
94
 
95
+ interface EmojiPickerLabels {
96
+ search: string;
97
+ noResults: string;
98
+ }
99
+ declare const DEFAULT_EMOJI_PICKER_LABELS: EmojiPickerLabels;
110
100
  interface EmojiPickerProps {
111
101
  onSelect: (emoji: string) => void;
102
+ labels?: Partial<EmojiPickerLabels>;
112
103
  className?: string;
113
104
  }
114
- declare const EmojiPicker: ({ onSelect, className }: EmojiPickerProps) => react.JSX.Element;
105
+ declare const EmojiPicker: ({ onSelect, labels, className }: EmojiPickerProps) => react.JSX.Element;
106
+
107
+ /**
108
+ * Catálogo de emojis com palavras-chave em português, usado pela busca do seletor.
109
+ *
110
+ * Fica em módulo próprio (e não dentro do `EmojiPicker`) porque a busca precisa varrer TODAS as
111
+ * categorias, não só a aberta — o índice é montado uma vez, no carregamento, e não a cada tecla.
112
+ *
113
+ * As palavras-chave são de busca, não legendas: incluem sinônimos e formas sem acento, porque quem
114
+ * digita rápido escreve "coracao" e "polegar" com a mesma frequência que a forma correta.
115
+ */
116
+ type EmojiEntry = {
117
+ readonly emoji: string;
118
+ readonly keywords: readonly string[];
119
+ };
120
+ type EmojiCategory = {
121
+ readonly name: string;
122
+ readonly entries: readonly EmojiEntry[];
123
+ };
124
+ declare const EMOJI_CATEGORIES: readonly EmojiCategory[];
125
+ /**
126
+ * Busca por prefixo de palavra-chave, não por substring: "ca" traz "casa" e "cartão", mas "sa" não
127
+ * traz "casa" — casar no meio da palavra devolvia resultado que ninguém consegue explicar.
128
+ */
129
+ declare function searchEmojis(query: string): readonly EmojiEntry[];
115
130
 
131
+ /**
132
+ * Mensagem pronta que o atendente cola no campo com um clique.
133
+ *
134
+ * `text` aceita string com `{{variavel}}` ou função: a string cobre o caso comum (copy fixa com o
135
+ * nome do cliente no meio) sem o host escrever código, e a função cobre o que precisa de lógica —
136
+ * escolher texto por produto, pluralizar, formatar moeda.
137
+ */
138
+ interface QuickReply {
139
+ key: string;
140
+ /** O que aparece no chip, emoji incluso: `👋 Saudação`. */
141
+ label: string;
142
+ text: string | ((variables: Readonly<Record<string, string>>) => string);
143
+ }
144
+ /**
145
+ * Troca `{{nome}}` pelos valores passados. Variável ausente vira string vazia, e não o literal
146
+ * `{{nome}}`: mandar "Olá {{nome}}!" para o cliente é pior que mandar "Olá !".
147
+ */
148
+ declare function applyQuickReplyVariables(template: string, variables?: Readonly<Record<string, string>>): string;
149
+ declare function resolveQuickReply(quickReply: QuickReply, variables?: Readonly<Record<string, string>>): string;
150
+ interface MessageComposerLabels {
151
+ emoji: string;
152
+ attach: string;
153
+ send: string;
154
+ }
155
+ declare const DEFAULT_MESSAGE_COMPOSER_LABELS: MessageComposerLabels;
116
156
  interface MessageComposerProps {
157
+ labels?: Partial<MessageComposerLabels>;
117
158
  onSend: (text: string) => void;
118
159
  onAttach?: (file: File) => void;
119
160
  value?: string;
@@ -123,10 +164,46 @@ interface MessageComposerProps {
123
164
  maxLength?: number;
124
165
  disabled?: boolean;
125
166
  acceptedFileTypes?: string;
167
+ /**
168
+ * Ocupa o lugar do botão de enviar enquanto não há nada para enviar — é onde o WhatsApp põe o
169
+ * microfone. Fora do campo, o botão vira um bloco solto ao lado do pill e quebra a barra.
170
+ */
171
+ idleAction?: ReactNode;
172
+ /** Mensagens prontas exibidas acima do campo. Vazio ou ausente, a faixa não é renderizada. */
173
+ quickReplies?: readonly QuickReply[];
174
+ /** Valores para `{{variavel}}` — tipicamente nome do cliente, produto, protocolo. */
175
+ quickReplyVariables?: Readonly<Record<string, string>>;
176
+ className?: string;
177
+ classNames?: Partial<MessageComposerClassNames>;
126
178
  }
127
- declare const MessageComposer: ({ onSend, onAttach, value: externalValue, onChange: externalOnChange, features, placeholder, maxLength, disabled, acceptedFileTypes, }: MessageComposerProps) => react.JSX.Element;
179
+ interface MessageComposerClassNames {
180
+ root: string;
181
+ quickReplies: string;
182
+ quickReply: string;
183
+ field: string;
184
+ }
185
+ /**
186
+ * Exatamente o que a Meta aceita em mensagem de mídia — imagem, sticker, áudio, vídeo e a lista
187
+ * fechada de documentos. Oferecer no seletor um formato que o WhatsApp recusa (`.zip`, `.rtf`)
188
+ * empurra a falha para depois do envio, quando já não dá para explicar ao operador o que houve.
189
+ * Produto com regra própria passa `acceptedFileTypes`.
190
+ */
191
+ declare const DEFAULT_ACCEPTED_FILE_TYPES: string;
192
+ declare const MessageComposer: ({ onSend, onAttach, value: externalValue, onChange: externalOnChange, quickReplies, quickReplyVariables, features, placeholder, maxLength, disabled, acceptedFileTypes, idleAction, className, classNames, labels, }: MessageComposerProps) => react.JSX.Element;
128
193
 
194
+ interface WhatsAppMessageEditorLabels {
195
+ bold: string;
196
+ /** Tooltip da negrito — traz a sintaxe do WhatsApp junto, por isso é separado do `aria-label`. */
197
+ boldHint: string;
198
+ italic: string;
199
+ italicHint: string;
200
+ strikethrough: string;
201
+ strikethroughHint: string;
202
+ insertPlaceholder: (token: string) => string;
203
+ }
204
+ declare const DEFAULT_WHATSAPP_MESSAGE_EDITOR_LABELS: WhatsAppMessageEditorLabels;
129
205
  interface WhatsAppMessageEditorProps {
206
+ labels?: Partial<WhatsAppMessageEditorLabels>;
130
207
  value: string;
131
208
  onChange: (value: string) => void;
132
209
  placeholder?: string;
@@ -135,7 +212,7 @@ interface WhatsAppMessageEditorProps {
135
212
  previewLabel?: string;
136
213
  emptyPreviewText?: string;
137
214
  }
138
- declare function WhatsAppMessageEditor({ value, onChange, placeholder, placeholders, rows, previewLabel, emptyPreviewText, }: WhatsAppMessageEditorProps): react.JSX.Element;
215
+ declare function WhatsAppMessageEditor({ value, onChange, placeholder, placeholders, rows, previewLabel, emptyPreviewText, labels, }: WhatsAppMessageEditorProps): react.JSX.Element;
139
216
 
140
217
  interface SimpleEmojiPickerProps {
141
218
  onSelect: (emoji: string) => void;
@@ -145,89 +222,58 @@ interface SimpleEmojiPickerProps {
145
222
  }
146
223
  declare function SimpleEmojiPicker({ onSelect, label, pickerWidth, pickerMaxHeight }: SimpleEmojiPickerProps): react.JSX.Element;
147
224
 
225
+ interface DateDividerClassNames {
226
+ root: string;
227
+ label: string;
228
+ }
148
229
  interface DateDividerProps {
149
230
  iso: string;
231
+ className?: string;
232
+ classNames?: Partial<DateDividerClassNames>;
150
233
  }
151
- declare function DateDivider({ iso }: DateDividerProps): react.JSX.Element;
234
+ declare function DateDivider({ iso, className, classNames }: DateDividerProps): react.JSX.Element;
152
235
 
236
+ interface AvatarLabels {
237
+ /** Lido por leitor de tela quando não há nome nem imagem — a silhueta genérica. */
238
+ unnamedContact: string;
239
+ }
153
240
  interface AvatarProps {
154
241
  name?: string | null;
155
242
  avatarUrl?: string;
156
243
  size?: 'sm' | 'md' | 'lg';
157
244
  className?: string;
245
+ labels?: Partial<AvatarLabels>;
158
246
  }
159
- declare function Avatar({ name, avatarUrl, size, className }: AvatarProps): react.JSX.Element;
160
-
161
- interface ConversationsApi {
162
- fetchMessages(conversationId: string, params?: {
163
- limit?: number;
164
- before?: string;
165
- }): Promise<MessagePayload[]>;
166
- fetchConversations(params?: {
167
- page?: number;
168
- limit?: number;
169
- waitingHuman?: boolean;
170
- search?: string;
171
- }): Promise<ConversationSummary[]>;
172
- sendMessage(conversationId: string, text: string): Promise<MessagePayload>;
173
- sendMedia(conversationId: string, data: {
174
- base64: string;
175
- mimeType: string;
176
- filename: string;
177
- caption?: string;
178
- }): Promise<MessagePayload>;
179
- sendTemplate(conversationId: string, data: {
180
- templateName: string;
181
- languageCode?: string;
182
- bodyParams?: string[];
183
- }): Promise<void>;
184
- markRead(conversationId: string): Promise<void>;
185
- getContext(conversationId: string): Promise<Record<string, unknown>>;
186
- getDocuments(conversationId: string, params?: {
187
- search?: string;
188
- page?: number;
189
- }): Promise<ConversationDocument[]>;
190
- getDocumentUrl(uploadId: string): Promise<string>;
191
- getMediaProxyUrl(mediaId: string): Promise<{
192
- mimeType: string;
193
- data: string;
194
- }>;
195
- }
196
- interface SSEProvider {
197
- connectConversationStream(conversationId: string): EventSource;
198
- connectGlobalStream(): EventSource;
199
- }
200
- interface ConversationSummary {
201
- id: string;
202
- whatsappNumber: string;
203
- clientName?: string;
204
- lastContent?: string;
205
- lastDirection?: 'inbound' | 'outbound';
206
- lastAt: string;
207
- lastInboundAt: string | null;
208
- mode: 'bot' | 'human';
209
- assignedUserId: string | null;
210
- waitingHuman: boolean;
211
- unread: number;
212
- currentState: string;
213
- }
214
- interface ConversationDocument {
215
- id: string;
216
- filename: string;
217
- mimeType: string;
218
- sizeBytes: number;
219
- source: string;
220
- linkedAt: string;
221
- }
247
+ declare const DEFAULT_AVATAR_LABELS: AvatarLabels;
248
+ declare function Avatar({ name, avatarUrl, size, className, labels }: AvatarProps): react.JSX.Element;
222
249
 
250
+ interface ConversationListItemLabels {
251
+ /** Tooltip do ponto vermelho: a janela de atendimento de 24h já fechou. */
252
+ expiredWindow: string;
253
+ /** Tooltip do ponto laranja: a janela de atendimento está perto de fechar. */
254
+ warningWindow: string;
255
+ }
256
+ declare const DEFAULT_CONVERSATION_LIST_ITEM_LABELS: ConversationListItemLabels;
223
257
  interface ConversationListItemProps {
224
258
  conversation: ConversationSummary;
259
+ labels?: Partial<ConversationListItemLabels>;
225
260
  active?: boolean;
226
261
  selected?: boolean;
227
262
  onClick?: () => void;
228
263
  onSelect?: (id: string) => void;
264
+ /**
265
+ * Desliga a borda inferior quando o item é composto dentro de outra linha (ver `ConversationRow`):
266
+ * com ela ligada, a borda corta a própria linha ao meio, separando o item do rodapé de status.
267
+ */
268
+ showDivider?: boolean;
269
+ /**
270
+ * Desliga o fundo de selecionado. Par do `showDivider`: quando o item é composto dentro de uma
271
+ * linha maior, quem pinta o realce é a linha — senão só o bloco do item fica cinza e o resto
272
+ * (checkbox, pills, barra lateral) continua branco, como se metade da linha estivesse selecionada.
273
+ */
274
+ highlightActive?: boolean;
229
275
  }
230
- declare const ConversationListItem: ({ conversation, active, selected, onClick, onSelect, }: ConversationListItemProps) => react.JSX.Element;
276
+ declare const ConversationListItem: ({ conversation, active, selected, onClick, onSelect, showDivider, highlightActive, labels, }: ConversationListItemProps) => react.JSX.Element;
231
277
 
232
278
  type ToastType = 'success' | 'error' | 'info';
233
279
  interface ToastContextValue {
@@ -247,12 +293,19 @@ interface StatusTicksProps {
247
293
  }
248
294
  declare function StatusTicks({ status, title }: StatusTicksProps): react.JSX.Element;
249
295
 
296
+ interface LightboxLabels {
297
+ /** Texto alternativo quando a imagem não tem legenda — sem ele o leitor de tela anuncia a URL. */
298
+ imageAlt: string;
299
+ close: string;
300
+ }
250
301
  interface LightboxProps {
251
302
  imageUrl: string;
252
303
  caption?: string;
253
304
  onClose: () => void;
305
+ labels?: Partial<LightboxLabels>;
254
306
  }
255
- declare function Lightbox({ imageUrl, caption, onClose }: LightboxProps): react.JSX.Element;
307
+ declare const DEFAULT_LIGHTBOX_LABELS: LightboxLabels;
308
+ declare function Lightbox({ imageUrl, caption, onClose, labels }: LightboxProps): react.JSX.Element;
256
309
 
257
310
  interface FileIconProps {
258
311
  filename?: string;
@@ -279,16 +332,404 @@ interface MessageTailProps {
279
332
  }
280
333
  declare function MessageTail({ isOutbound }: MessageTailProps): react.JSX.Element;
281
334
 
335
+ /**
336
+ * Janela de sessão: o intervalo em que o canal aceita mensagem livre do atendente. No WhatsApp são
337
+ * 24h desde o último contato do cliente; fora dela só template. Cada canal tem a sua regra — e há
338
+ * canal sem janela nenhuma — então a política vem de `capabilitiesOf`, não de constante fixa.
339
+ *
340
+ * Mora no SDK porque é regra de plataforma, não de produto: todo projeto que usa este pacote
341
+ * precisa dela para saber o que o atendente ainda consegue fazer.
342
+ */
343
+
344
+ declare const CONVERSATION_WINDOW: {
345
+ readonly ALL: "all";
346
+ readonly FRESH: "fresh";
347
+ readonly WARNING: "warning";
348
+ readonly CRITICAL: "critical";
349
+ readonly EXPIRED: "expired";
350
+ };
351
+ type ConversationWindow = (typeof CONVERSATION_WINDOW)[keyof typeof CONVERSATION_WINDOW];
352
+ declare const WINDOW_FILTERS: readonly [{
353
+ readonly value: "all";
354
+ readonly label: "Todas";
355
+ readonly dotClass: "";
356
+ }, {
357
+ readonly value: "fresh";
358
+ readonly label: "<12h";
359
+ readonly dotClass: "bg-green-500";
360
+ }, {
361
+ readonly value: "warning";
362
+ readonly label: "12-21h";
363
+ readonly dotClass: "bg-yellow-500";
364
+ }, {
365
+ readonly value: "critical";
366
+ readonly label: "21-24h";
367
+ readonly dotClass: "bg-red-500";
368
+ }, {
369
+ readonly value: "expired";
370
+ readonly label: ">24h";
371
+ readonly dotClass: "bg-gray-400";
372
+ }];
373
+ type WindowOfParams = {
374
+ readonly lastInboundAt: string | null;
375
+ readonly now: number;
376
+ /** Ausente = `whatsapp`. */
377
+ readonly channel?: ConversationChannel | undefined;
378
+ };
379
+ /**
380
+ * Sem `lastInboundAt` o cliente nunca escreveu, então não há janela aberta — classificar como
381
+ * expirada é o comportamento seguro: evita o atendente tentar texto livre e receber recusa.
382
+ *
383
+ * Canal sem janela de sessão (chat de site) é sempre `fresh`: ali nada expira, e marcar expirado
384
+ * bloquearia o composer inventando um limite que a plataforma não impõe.
385
+ */
386
+ declare function windowOf(params: WindowOfParams): ConversationWindow;
387
+ declare function formatStalledFor(lastAt: string, now: number): string;
388
+
389
+ type ConversationRowClassNames = {
390
+ root: string;
391
+ windowBar: string;
392
+ };
393
+ type ConversationRowProps = {
394
+ conversation: ConversationSummary;
395
+ active: boolean;
396
+ selected: boolean;
397
+ now: number;
398
+ busy: boolean;
399
+ onOpen: () => void;
400
+ onToggleSelected: () => void;
401
+ onTakeover: () => void;
402
+ className?: string;
403
+ classNames?: Partial<ConversationRowClassNames>;
404
+ };
405
+ declare function ConversationRow({ conversation, active, selected, now, busy, onOpen, onToggleSelected, onTakeover, className, classNames, }: ConversationRowProps): react.JSX.Element;
406
+
407
+ declare const CHANNEL_BRAND_COLOR: Readonly<Record<ConversationChannel, string>>;
408
+ interface ChannelIconProps {
409
+ channel?: ConversationChannel | undefined;
410
+ size?: number;
411
+ className?: string;
412
+ }
413
+ declare function ChannelIcon({ channel, size, className }: ChannelIconProps): react.JSX.Element;
414
+
415
+ interface ConversationHeaderLabels {
416
+ botMode: string;
417
+ humanMode: string;
418
+ returnToBot: string;
419
+ finish: string;
420
+ takeover: string;
421
+ download: string;
422
+ documents: string;
423
+ back: string;
424
+ moreActions: string;
425
+ }
426
+ declare const DEFAULT_CONVERSATION_HEADER_LABELS: ConversationHeaderLabels;
427
+ /**
428
+ * Partes estilizáveis do cabeçalho. Cada chave recebe classes que o `cn` funde por cima da base, e
429
+ * conflito de utilitário (padding, gap, borda) fica com o valor do produto.
430
+ */
431
+ interface ConversationHeaderClassNames {
432
+ root: string;
433
+ identity: string;
434
+ name: string;
435
+ meta: string;
436
+ actions: string;
437
+ desktopActions: string;
438
+ mobileMenu: string;
439
+ }
440
+ /**
441
+ * Utilitário extra que o host pendura no cabeçalho — ícone no desktop, item de menu no celular,
442
+ * como os nativos. Entra por aqui, e não por um slot de ReactNode, porque é isso que preserva o
443
+ * comportamento responsivo: um nó solto viraria um quarto ícone em 375px, sem área de toque.
444
+ */
445
+ interface ConversationHeaderUtility {
446
+ key: string;
447
+ /** Emoji, para casar com os utilitários nativos do cabeçalho. */
448
+ icon: string;
449
+ label: string;
450
+ run: () => void;
451
+ active?: boolean;
452
+ }
453
+ interface ConversationHeaderProps {
454
+ conversation: ConversationSummary;
455
+ busy?: boolean;
456
+ onTakeover?: () => void;
457
+ onReturnToBot?: () => void;
458
+ onFinish?: () => void;
459
+ onDownload?: () => void;
460
+ onOpenDocuments?: () => void;
461
+ documentsOpen?: boolean;
462
+ /** Ações do produto que não existem no contrato do pacote (ex.: ferramentas de dev). */
463
+ extraUtilities?: readonly ConversationHeaderUtility[];
464
+ onBack?: () => void;
465
+ labels?: Partial<ConversationHeaderLabels>;
466
+ className?: string;
467
+ classNames?: Partial<ConversationHeaderClassNames>;
468
+ }
469
+ declare function ConversationHeader({ conversation, busy, onTakeover, onReturnToBot, onFinish, onDownload, onOpenDocuments, documentsOpen, extraUtilities, onBack, labels: labelsOverride, className, classNames, }: ConversationHeaderProps): react.JSX.Element;
470
+
471
+ /**
472
+ * "Suas Seleções": o que o bot já coletou na conversa. Para o atendente que assume no meio, é a
473
+ * diferença entre ler o transcript inteiro e ver o estado em duas linhas.
474
+ *
475
+ * O pacote não interpreta o contexto — ele é `Record<string, unknown>` e cada produto nomeia as
476
+ * próprias chaves. O host traduz para `entries`; aqui só se decide como mostrar.
477
+ */
478
+ interface ConversationContextEntry {
479
+ key: string;
480
+ label: string;
481
+ value?: string | undefined;
482
+ icon?: string;
483
+ }
484
+ interface ConversationContextPanelLabels {
485
+ title: string;
486
+ empty: string;
487
+ }
488
+ declare const DEFAULT_CONVERSATION_CONTEXT_LABELS: ConversationContextPanelLabels;
489
+ interface ConversationContextPanelClassNames {
490
+ root: string;
491
+ toggle: string;
492
+ counter: string;
493
+ body: string;
494
+ }
495
+ interface ConversationContextPanelProps {
496
+ entries: readonly ConversationContextEntry[];
497
+ labels?: Partial<ConversationContextPanelLabels>;
498
+ className?: string;
499
+ classNames?: Partial<ConversationContextPanelClassNames>;
500
+ }
501
+ declare function ConversationContextPanel({ entries, labels: labelsOverride, className, classNames, }: ConversationContextPanelProps): react.JSX.Element;
502
+
503
+ interface WindowExpiredNoticeLabels {
504
+ title: string;
505
+ description: string;
506
+ sendTemplate: string;
507
+ }
508
+ declare const DEFAULT_WINDOW_EXPIRED_LABELS: WindowExpiredNoticeLabels;
509
+ interface WindowExpiredNoticeProps {
510
+ onSendTemplate?: () => void;
511
+ disabled?: boolean;
512
+ labels?: Partial<WindowExpiredNoticeLabels>;
513
+ className?: string;
514
+ }
515
+ declare function WindowExpiredNotice({ onSendTemplate, disabled, labels: labelsOverride, className, }: WindowExpiredNoticeProps): react.JSX.Element;
516
+ /**
517
+ * Só a faixa `expired` bloqueia: 21-24h ainda aceita texto livre e merece alerta, não impedimento.
518
+ */
519
+ declare function isWindowBlocking(window: ConversationWindow): boolean;
520
+
521
+ /**
522
+ * Biblioteca de arquivos de TODAS as conversas — a tela de Documentos do painel, fora do
523
+ * atendimento.
524
+ *
525
+ * Distinta do `ConversationDocumentsPanel`: aquele parte de uma conversa aberta e vive dentro dela;
526
+ * esta varre a empresa e por isso mostra de qual conversa cada arquivo veio, com o número
527
+ * clicável. Sem essa referência, uma lista global de anexos não responde nenhuma pergunta.
528
+ */
529
+ interface DocumentsLibraryLabels {
530
+ title: string;
531
+ searchPlaceholder: string;
532
+ empty: string;
533
+ noResults: string;
534
+ loading: string;
535
+ failure: string;
536
+ view: string;
537
+ download: string;
538
+ openConversation: string;
539
+ sourceFilterAll: string;
540
+ sourceFilterCustomer: string;
541
+ sourceFilterTeam: string;
542
+ sortMostRecent: string;
543
+ sortOldest: string;
544
+ clearFilters: string;
545
+ total: (count: number) => string;
546
+ page: (current: number, last: number) => string;
547
+ }
548
+ declare const DEFAULT_DOCUMENTS_LIBRARY_LABELS: DocumentsLibraryLabels;
549
+ interface DocumentsLibraryClassNames {
550
+ root: string;
551
+ title: string;
552
+ filters: string;
553
+ search: string;
554
+ sourceSelect: string;
555
+ sortButton: string;
556
+ clearButton: string;
557
+ status: string;
558
+ list: string;
559
+ item: string;
560
+ conversationLink: string;
561
+ filename: string;
562
+ meta: string;
563
+ pagination: string;
564
+ }
565
+ interface DocumentsLibraryProps {
566
+ /** Itens por página. O total vem do servidor. */
567
+ perPage?: number;
568
+ /** Abrir a conversa de origem. Ausente, o número aparece como texto e não como link. */
569
+ onOpenConversation?: (conversationId: string) => void;
570
+ labels?: Partial<DocumentsLibraryLabels>;
571
+ className?: string;
572
+ classNames?: Partial<DocumentsLibraryClassNames>;
573
+ }
574
+ declare function DocumentsLibrary({ perPage, onOpenConversation, labels: labelsOverride, className, classNames, }: DocumentsLibraryProps): react.JSX.Element | null;
575
+
576
+ /**
577
+ * Arquivos trocados na conversa. Sai do transcript e vira lista própria porque anexo é o que o
578
+ * atendente mais precisa reencontrar depois — rolar meses de mensagens para achar um comprovante é
579
+ * o caso que a busca por documento existe para eliminar.
580
+ *
581
+ * Usa `useConversationDocuments`, então funciona com qualquer `ConversationsApi`. Host sem
582
+ * biblioteca de documentos cai no estado vazio, sem quebrar.
583
+ */
584
+ /** Origens que o filtro oferece. `all` não é origem — é a ausência de filtro. */
585
+ declare const DOCUMENT_SOURCE_FILTER: {
586
+ readonly ALL: "all";
587
+ readonly CUSTOMER: "customer";
588
+ readonly TEAM: "team";
589
+ };
590
+ type DocumentSourceFilter = (typeof DOCUMENT_SOURCE_FILTER)[keyof typeof DOCUMENT_SOURCE_FILTER];
591
+ interface ConversationDocumentsPanelLabels {
592
+ toggle: string;
593
+ title: string;
594
+ searchPlaceholder: string;
595
+ empty: string;
596
+ /** Distinto de `empty`: sem resultado POR CAUSA do filtro, e não conversa sem anexo nenhum. */
597
+ noResults: string;
598
+ loading: string;
599
+ failure: string;
600
+ download: string;
601
+ view: string;
602
+ sourceFilterAll: string;
603
+ sourceFilterCustomer: string;
604
+ sourceFilterTeam: string;
605
+ sortMostRecent: string;
606
+ sortOldest: string;
607
+ clearFilters: string;
608
+ selectAll: string;
609
+ downloadSelected: (count: number) => string;
610
+ archiveFailed: string;
611
+ total: (count: number) => string;
612
+ page: (current: number, last: number) => string;
613
+ }
614
+ declare const DEFAULT_CONVERSATION_DOCUMENTS_LABELS: ConversationDocumentsPanelLabels;
615
+ /**
616
+ * Partes estilizáveis do painel, no mesmo contrato do `ConversationHeader`: `cn` funde por cima da
617
+ * base e conflito de utilitário (padding, tamanho de fonte, borda) fica com o valor do produto.
618
+ *
619
+ * `status` cobre carregando/erro/vazio de uma vez — são a mesma linha de texto auxiliar, e slots
620
+ * separados só multiplicariam chave para quem quer mudar a cor de aviso.
621
+ */
622
+ interface ConversationDocumentsPanelClassNames {
623
+ root: string;
624
+ body: string;
625
+ title: string;
626
+ filters: string;
627
+ search: string;
628
+ sourceSelect: string;
629
+ sortButton: string;
630
+ clearButton: string;
631
+ status: string;
632
+ list: string;
633
+ item: string;
634
+ sourceBadge: string;
635
+ filename: string;
636
+ meta: string;
637
+ viewButton: string;
638
+ downloadButton: string;
639
+ pagination: string;
640
+ selectionBar: string;
641
+ checkbox: string;
642
+ }
643
+ interface ConversationDocumentsPanelProps {
644
+ conversationId: string;
645
+ /** Controlado de fora porque o gatilho vive no cabeçalho, junto das outras ações da conversa. */
646
+ open: boolean;
647
+ /** Itens por página. O total vem do servidor; sem paginação no host, a barra não aparece. */
648
+ perPage?: number;
649
+ labels?: Partial<ConversationDocumentsPanelLabels>;
650
+ className?: string;
651
+ classNames?: Partial<ConversationDocumentsPanelClassNames>;
652
+ }
653
+ declare function ConversationDocumentsPanel({ conversationId, open, perPage, labels: labelsOverride, className, classNames, }: ConversationDocumentsPanelProps): react.JSX.Element | null;
654
+
655
+ /**
656
+ * Serialização do transcript para download. Fica no pacote porque o formato de um histórico de
657
+ * WhatsApp legível não é regra de negócio de ninguém — e porque o host que já tem as mensagens em
658
+ * tela não deveria precisar de rota nova só para salvar um arquivo.
659
+ *
660
+ * Funções puras, separadas do disparo do download: é o que permite testá-las sem DOM.
661
+ */
662
+
663
+ type BuildTranscriptTextParams = {
664
+ readonly messages: readonly MessagePayload[];
665
+ readonly whatsappNumber: string;
666
+ readonly clientName?: string | undefined;
667
+ };
668
+ /**
669
+ * Formato próximo ao export nativo do WhatsApp (`[data hora] Autor: texto`), que é o que pessoas e
670
+ * ferramentas de suporte já sabem ler.
671
+ */
672
+ declare function buildTranscriptText(params: BuildTranscriptTextParams): string;
673
+ declare function buildTranscriptFilename(whatsappNumber: string, generatedAt: Date): string;
674
+ /**
675
+ * Dispara o download no navegador. `revokeObjectURL` no fim não é higiene opcional: sem ele cada
676
+ * export retém o blob inteiro em memória até a aba fechar.
677
+ */
678
+ declare function downloadTextFile(filename: string, content: string): void;
679
+
680
+ /**
681
+ * Leitura passiva do tema do host: observa a classe `dark` no `<html>` e não escreve nada.
682
+ *
683
+ * Existe porque `useDarkMode` é um controlador — ele grava a classe e persiste a preferência. Um
684
+ * componente que só precisa escolher cor não pode usá-lo: bastava renderizar o mapa de fluxos
685
+ * para o app inteiro do host trocar de tema, seguindo o `prefers-color-scheme` do sistema em vez
686
+ * da configuração da aplicação.
687
+ */
688
+ declare function useIsDarkTheme(): boolean;
282
689
  declare function useDarkMode(): {
283
690
  isDark: boolean;
284
691
  toggle: () => void;
285
692
  };
286
693
 
694
+ /**
695
+ * Detecta tela estreita para decisões que CSS não resolve — como abrir ou não um painel por padrão.
696
+ *
697
+ * O breakpoint é o mesmo das classes `cv-only-*` e `.cv-back` (1024px). Duplicado aqui porque
698
+ * JavaScript não lê media query do stylesheet; se um dia divergirem, o sintoma é painel abrindo
699
+ * numa largura onde o resto da UI já mudou de modo.
700
+ */
701
+ declare const NARROW_MAX_WIDTH_PX = 1023;
702
+ declare function useIsNarrow(): boolean;
703
+
704
+ interface UseWaitingNotificationsLabels {
705
+ /** Título da notificação do sistema. Recebe a conversa para o host escolher nome × número. */
706
+ title: (conversation: ConversationSummary) => string;
707
+ body: (conversation: ConversationSummary) => string;
708
+ }
709
+ interface UseWaitingNotificationsParams {
710
+ /**
711
+ * Repassado cru ao `fetchConversations`. É o que permite filtrar não lidas **no servidor** em
712
+ * vez de baixar a lista inteira e contar no cliente: um painel com milhares de conversas não
713
+ * pode paginar 50 por vez atrás de quem tem `unread > 0`.
714
+ */
715
+ readonly params?: ListConversationsParams;
716
+ readonly intervalMs?: number;
717
+ /** Desliga o polling sem desmontar quem chama — útil com a aba em segundo plano. */
718
+ readonly enabled?: boolean;
719
+ readonly icon?: string;
720
+ readonly labels?: Partial<UseWaitingNotificationsLabels>;
721
+ }
287
722
  interface UseWaitingNotificationsResult {
288
723
  unreadCount: number;
289
724
  conversations: ConversationSummary[];
725
+ /**
726
+ * Releitura sob demanda. Existe porque o polling é o piso, não o mecanismo: quem já recebe SSE
727
+ * ou acabou de marcar tudo como lido sabe da mudança antes do próximo tick, e esperar 10s para
728
+ * o contador acompanhar faz a interface parecer travada.
729
+ */
730
+ refresh: () => Promise<void>;
290
731
  }
291
- declare function useWaitingNotifications(): UseWaitingNotificationsResult;
732
+ declare function useWaitingNotifications(params?: UseWaitingNotificationsParams): UseWaitingNotificationsResult;
292
733
 
293
734
  interface ConversationsContextValue {
294
735
  api: ConversationsApi;
@@ -370,6 +811,7 @@ interface WhatsAppCreateTemplateFormLabels {
370
811
  sectionDescription: string;
371
812
  nameLabel: string;
372
813
  nameHint: string;
814
+ namePlaceholder: string;
373
815
  categoryLabel: string;
374
816
  languageLabel: string;
375
817
  headerLabel: string;
@@ -435,6 +877,41 @@ interface WelcomeFarewellFormProps {
435
877
  }
436
878
  declare function WelcomeFarewellForm({ welcomeMessage, onWelcomeMessageChange, farewellMessage, onFarewellMessageChange, onSave, saving, saveSuccess, welcomePlaceholders, farewellPlaceholders, labels: labelsOverride, }: WelcomeFarewellFormProps): react.JSX.Element;
437
879
 
880
+ declare const TEMPLATE_SETTINGS_TAB: {
881
+ readonly SELECT: "select";
882
+ readonly CREATE: "create";
883
+ };
884
+ type TemplateSettingsTab = (typeof TEMPLATE_SETTINGS_TAB)[keyof typeof TEMPLATE_SETTINGS_TAB];
885
+ interface WhatsAppTemplatesSettingsLabels {
886
+ selectTab: string;
887
+ createTab: string;
888
+ }
889
+ declare const DEFAULT_TEMPLATES_SETTINGS_LABELS: WhatsAppTemplatesSettingsLabels;
890
+ interface WhatsAppTemplatesSettingsProps {
891
+ templates: WhatsAppTemplateSummary[];
892
+ loadingTemplates?: boolean;
893
+ templatesError?: boolean;
894
+ onRefreshTemplates?: () => void;
895
+ selectedTemplateName: string;
896
+ onSelectTemplate: (name: string, template: WhatsAppTemplateSummary | undefined) => void;
897
+ variables: string[];
898
+ onVariablesChange: (variables: string[]) => void;
899
+ availableVariables?: WhatsAppTemplateVariableSuggestion[];
900
+ saving?: boolean;
901
+ saveSuccess?: boolean;
902
+ onSave: (event: FormEvent) => void;
903
+ /** Ausente = host não sabe criar template; a aba de criação nem aparece. */
904
+ create?: {
905
+ value: WhatsAppCreateTemplateState;
906
+ onChange: (value: WhatsAppCreateTemplateState) => void;
907
+ onSubmit: (event: FormEvent) => void;
908
+ submitting?: boolean;
909
+ result?: WhatsAppCreateTemplateResult | null;
910
+ };
911
+ labels?: Partial<WhatsAppTemplatesSettingsLabels>;
912
+ }
913
+ declare function WhatsAppTemplatesSettings({ labels: labelsOverride, create, ...settingsProps }: WhatsAppTemplatesSettingsProps): react.JSX.Element;
914
+
438
915
  interface TopicItem {
439
916
  key: string;
440
917
  label: string;
@@ -472,7 +949,7 @@ interface UseConversationMessagesResult {
472
949
  caption?: string;
473
950
  }) => Promise<MessagePayload>;
474
951
  sendTemplate: (data: {
475
- templateName: string;
952
+ templateName?: string;
476
953
  languageCode?: string;
477
954
  bodyParams?: string[];
478
955
  }) => Promise<void>;
@@ -483,14 +960,11 @@ declare function useConversationMessages(conversationId: string, params?: {
483
960
  before?: string;
484
961
  }): UseConversationMessagesResult;
485
962
 
486
- interface UseConversationListParams {
487
- page?: number;
488
- limit?: number;
489
- waitingHuman?: boolean;
490
- search?: string;
491
- }
963
+ type UseConversationListParams = ListConversationsParams;
492
964
  interface UseConversationListResult {
493
965
  conversations: ConversationSummary[];
966
+ /** Total no servidor. Cai para o tamanho da página quando a API devolve só o array. */
967
+ total: number;
494
968
  loading: boolean;
495
969
  error: Error | undefined;
496
970
  refetch: () => Promise<void>;
@@ -505,22 +979,42 @@ interface UseConversationContextResult {
505
979
  }
506
980
  declare function useConversationContext(conversationId: string): UseConversationContextResult;
507
981
 
508
- interface UseConversationDocumentsParams {
509
- search?: string;
510
- page?: number;
511
- }
982
+ type UseConversationDocumentsParams = ListDocumentsParams;
512
983
  interface UseConversationDocumentsResult {
513
984
  documents: ConversationDocument[];
985
+ /** Total no servidor. Cai para o tamanho da página quando a API devolve só o array. */
986
+ total: number;
514
987
  loading: boolean;
515
988
  error: Error | undefined;
516
989
  refetch: () => Promise<void>;
517
990
  }
518
- declare function useConversationDocuments(conversationId: string, params?: UseConversationDocumentsParams): UseConversationDocumentsResult;
991
+ declare function useConversationDocuments(conversationId: string | undefined, params?: UseConversationDocumentsParams): UseConversationDocumentsResult;
519
992
 
520
993
  type ConversationRealtimeHandler = (event: MessageEvent) => void;
521
994
  declare function useConversationRealtime(conversationId: string | undefined, onEvent: ConversationRealtimeHandler): void;
522
995
  declare function useGlobalRealtime(onEvent: ConversationRealtimeHandler): void;
523
996
 
997
+ interface UseConversationActionsResult {
998
+ /** `undefined` quando a API do host não implementa a operação — a UI esconde a afordância. */
999
+ takeover: (() => Promise<void>) | undefined;
1000
+ release: (() => Promise<void>) | undefined;
1001
+ finalize: (() => Promise<void>) | undefined;
1002
+ }
1003
+ /**
1004
+ * Ações de atendimento de UMA conversa, já ligadas ao id.
1005
+ *
1006
+ * Separado de `useConversationMessages` porque assumir e devolver conversa também acontece a
1007
+ * partir da lista, onde nenhuma thread está aberta — embutir nas mensagens obrigaria a carregar
1008
+ * a thread inteira só para desenhar um botão na linha.
1009
+ */
1010
+ declare function useConversationActions(conversationId: string): UseConversationActionsResult;
1011
+ interface UseInboxActionsResult {
1012
+ markAllRead: (() => Promise<void>) | undefined;
1013
+ listTemplates: (() => Promise<ConversationTemplate[]>) | undefined;
1014
+ }
1015
+ /** Ações que valem para a caixa inteira, sem conversa selecionada. */
1016
+ declare function useInboxActions(): UseInboxActionsResult;
1017
+
524
1018
  declare function parseWhatsAppFormatting(text: string): ReactNode[];
525
1019
  declare function waToHTML(text: string): string;
526
1020
  declare function htmlToWA(html: string): string;
@@ -530,6 +1024,8 @@ declare function formatPhone(number: string): string;
530
1024
  declare function phoneInitials(number: string): string;
531
1025
 
532
1026
  declare function formatTimestamp(timestamp: string): string;
1027
+ declare function formatDateTime(iso: string): string;
1028
+ declare function isSameDay(a: Date, b: Date): boolean;
533
1029
  declare function formatFileSize(bytes: number): string;
534
1030
 
535
1031
  interface AsyncResourceState<T> {
@@ -539,4 +1035,16 @@ interface AsyncResourceState<T> {
539
1035
  refetch: () => Promise<void>;
540
1036
  }
541
1037
 
542
- export { type AsyncResourceState, AudioPlayer, type AudioPlayerProps, Avatar, type AvatarProps, type ConversationDocument, ConversationListItem, type ConversationListItemProps, type ConversationLocales, ConversationLocalesProvider, type ConversationLocalesProviderProps, type ConversationRealtimeHandler, type ConversationSummary, ConversationWallpaper, type ConversationWallpaperProps, type ConversationsApi, type ConversationsFeatures, ConversationsProvider, type ConversationsTheme, type ConversationsUIConfig, DateDivider, type DateDividerProps, EmojiPicker, type EmojiPickerProps, FileIcon, type FileIconProps, Lightbox, type LightboxProps, MediaRenderer, type MediaRendererProps, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerProps, type MessagePayload, MessageTail, MessageText, type MessageTextProps, MessageTimestamp, type ResolveMediaUrl, type SSEProvider, SimpleEmojiPicker, type SimpleEmojiPickerProps, StatusTicks, type StatusTicksProps, ToastProvider, type TopicItem, TopicsForm, type TopicsFormLabels, type TopicsFormProps, type UseConversationContextResult, type UseConversationDocumentsParams, type UseConversationDocumentsResult, type UseConversationListParams, type UseConversationListResult, type UseConversationMessagesResult, WelcomeFarewellForm, type WelcomeFarewellFormLabels, type WelcomeFarewellFormProps, WhatsAppCreateTemplateForm, type WhatsAppCreateTemplateFormLabels, type WhatsAppCreateTemplateFormProps, type WhatsAppCreateTemplateResult, type WhatsAppCreateTemplateState, WhatsAppMessageEditor, type WhatsAppMessageEditorProps, type WhatsAppTemplateHeaderType, WhatsAppTemplateSettingsForm, type WhatsAppTemplateSettingsFormLabels, type WhatsAppTemplateSettingsFormProps, type WhatsAppTemplateSummary, type WhatsAppTemplateVariableSuggestion, formatFileSize, formatPhone, formatTimestamp, htmlToWA, parseWhatsAppFormatting, phoneInitials, toast, useConversationContext, useConversationDocuments, useConversationList, useConversationLocales, useConversationMessages, useConversationRealtime, useConversations, useDarkMode, useGlobalRealtime, useToast, useWaitingNotifications, waToHTML, waToHTMLInline };
1038
+ /**
1039
+ * Resolve a mídia de uma mensagem numa URL exibível, usando só o `ConversationsApi`.
1040
+ *
1041
+ * Mora no pacote, e não em cada host, porque a regra não tem nada de específico de produto: é a
1042
+ * tradução de `uploadId`/`mediaId` pelos dois métodos que o próprio contrato já declara. Deixá-la no
1043
+ * host significava que todo projeto que adotasse o SDK reescreveria as mesmas oito linhas — e, na
1044
+ * prática, ninguém escrevia: o `MediaRenderer` só busca mídia pela porta `onResolveMediaUrl`, então
1045
+ * onde nada era injetado foto, vídeo e áudio ficavam no placeholder para sempre.
1046
+ */
1047
+
1048
+ declare function createMediaUrlResolver(api: Pick<ConversationsApi, 'getDocumentUrl' | 'getMediaProxyUrl'>): ResolveMediaUrl;
1049
+
1050
+ export { type AsyncResourceState, AudioPlayer, type AudioPlayerProps, Avatar, type AvatarLabels, type AvatarProps, type BuildTranscriptTextParams, CHANNEL_BRAND_COLOR, CONVERSATION_WINDOW, ChannelIcon, type ChannelIconProps, ConversationChannel, type ConversationContextEntry, ConversationContextPanel, type ConversationContextPanelClassNames, type ConversationContextPanelLabels, type ConversationContextPanelProps, ConversationDocument, ConversationDocumentsPanel, type ConversationDocumentsPanelClassNames, type ConversationDocumentsPanelLabels, type ConversationDocumentsPanelProps, ConversationHeader, type ConversationHeaderClassNames, type ConversationHeaderLabels, type ConversationHeaderProps, type ConversationHeaderUtility, ConversationListItem, type ConversationListItemLabels, type ConversationListItemProps, type ConversationLocales, ConversationLocalesProvider, type ConversationLocalesProviderProps, type ConversationRealtimeHandler, ConversationRow, type ConversationRowClassNames, type ConversationRowProps, ConversationSummary, ConversationTemplate, ConversationWallpaper, type ConversationWallpaperProps, type ConversationWindow, ConversationsApi, ConversationsFeatures, ConversationsProvider, DEFAULT_ACCEPTED_FILE_TYPES, DEFAULT_AVATAR_LABELS, DEFAULT_CONVERSATION_CONTEXT_LABELS, DEFAULT_CONVERSATION_DOCUMENTS_LABELS, DEFAULT_CONVERSATION_HEADER_LABELS, DEFAULT_CONVERSATION_LIST_ITEM_LABELS, DEFAULT_DOCUMENTS_LIBRARY_LABELS, DEFAULT_EMOJI_PICKER_LABELS, DEFAULT_INTERACTIVE_MESSAGE_LABELS, DEFAULT_LIGHTBOX_LABELS, DEFAULT_MESSAGE_COMPOSER_LABELS, DEFAULT_TEMPLATES_SETTINGS_LABELS, DEFAULT_WHATSAPP_MESSAGE_EDITOR_LABELS, DEFAULT_WINDOW_EXPIRED_LABELS, DOCUMENT_SOURCE_FILTER, DateDivider, type DateDividerClassNames, type DateDividerProps, type DocumentSourceFilter, DocumentsLibrary, type DocumentsLibraryClassNames, type DocumentsLibraryLabels, type DocumentsLibraryProps, EMOJI_CATEGORIES, type EmojiCategory, type EmojiEntry, EmojiPicker, type EmojiPickerLabels, type EmojiPickerProps, FileIcon, type FileIconProps, InteractiveMessage, type InteractiveMessageLabels, type InteractiveMessageProps, InteractivePayload, InteractiveSelection, Lightbox, type LightboxLabels, type LightboxProps, ListConversationsParams, ListDocumentsParams, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerClassNames, type MessageComposerLabels, type MessageComposerProps, MessagePayload, MessageTail, MessageText, type MessageTextProps, MessageTimestamp, NARROW_MAX_WIDTH_PX, type QuickReply, ResolveMediaUrl, SSEProvider, SimpleEmojiPicker, type SimpleEmojiPickerProps, StatusTicks, type StatusTicksProps, TEMPLATE_SETTINGS_TAB, type TemplateSettingsTab, ToastProvider, type TopicItem, TopicsForm, type TopicsFormLabels, type TopicsFormProps, type UseConversationActionsResult, type UseConversationContextResult, type UseConversationDocumentsParams, type UseConversationDocumentsResult, type UseConversationListParams, type UseConversationListResult, type UseConversationMessagesResult, type UseInboxActionsResult, type UseWaitingNotificationsLabels, type UseWaitingNotificationsParams, type UseWaitingNotificationsResult, WINDOW_FILTERS, WelcomeFarewellForm, type WelcomeFarewellFormLabels, type WelcomeFarewellFormProps, WhatsAppCreateTemplateForm, type WhatsAppCreateTemplateFormLabels, type WhatsAppCreateTemplateFormProps, type WhatsAppCreateTemplateResult, type WhatsAppCreateTemplateState, WhatsAppMessageEditor, type WhatsAppMessageEditorLabels, type WhatsAppMessageEditorProps, type WhatsAppTemplateHeaderType, WhatsAppTemplateSettingsForm, type WhatsAppTemplateSettingsFormLabels, type WhatsAppTemplateSettingsFormProps, type WhatsAppTemplateSummary, type WhatsAppTemplateVariableSuggestion, WhatsAppTemplatesSettings, type WhatsAppTemplatesSettingsLabels, type WhatsAppTemplatesSettingsProps, WindowExpiredNotice, type WindowExpiredNoticeLabels, type WindowExpiredNoticeProps, type WindowOfParams, applyQuickReplyVariables, buildTranscriptFilename, buildTranscriptText, createMediaUrlResolver, downloadTextFile, formatDateTime, formatFileSize, formatPhone, formatStalledFor, formatTimestamp, htmlToWA, isSameDay, isWindowBlocking, parseWhatsAppFormatting, phoneInitials, resolveQuickReply, searchEmojis, toast, useConversationActions, useConversationContext, useConversationDocuments, useConversationList, useConversationLocales, useConversationMessages, useConversationRealtime, useConversations, useDarkMode, useGlobalRealtime, useInboxActions, useIsDarkTheme, useIsNarrow, useToast, useWaitingNotifications, waToHTML, waToHTMLInline, windowOf };