@adatechnology/conversations-ui 0.1.0-rc.2 → 0.1.0-rc.4

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 (53) hide show
  1. package/dist/chunk-4R6Y43DQ.js +726 -0
  2. package/dist/chunk-NV2RZ5KT.js +56 -0
  3. package/dist/{chunk-ZDURDZTM.js → chunk-OGRRHQQW.js} +1 -41
  4. package/dist/flows/index.js +6 -4
  5. package/dist/index.d.ts +323 -111
  6. package/dist/index.js +1032 -954
  7. package/dist/preview/index.d.ts +172 -0
  8. package/dist/preview/index.js +576 -0
  9. package/dist/styles.css +198 -0
  10. package/dist/types-C0PtaO7S.d.ts +207 -0
  11. package/package.json +10 -3
  12. package/src/Avatar.tsx +18 -3
  13. package/src/ChannelIcon.tsx +87 -0
  14. package/src/ConversationContextPanel.tsx +106 -0
  15. package/src/ConversationDocumentsPanel.tsx +107 -0
  16. package/src/ConversationHeader.tsx +239 -0
  17. package/src/ConversationListItem.tsx +36 -5
  18. package/src/ConversationLocalesProvider.tsx +16 -0
  19. package/src/ConversationRow.tsx +137 -0
  20. package/src/DateDivider.tsx +16 -3
  21. package/src/MediaRenderer.tsx +9 -9
  22. package/src/MessageBubble.tsx +24 -2
  23. package/src/MessageComposer.tsx +15 -2
  24. package/src/Wallpaper.tsx +4 -2
  25. package/src/WindowExpiredNotice.tsx +57 -0
  26. package/src/conversationChannel.test.ts +53 -0
  27. package/src/conversationChannel.ts +146 -0
  28. package/src/conversationTranscript.test.ts +65 -0
  29. package/src/conversationTranscript.ts +64 -0
  30. package/src/conversationWindow.test.ts +90 -0
  31. package/src/conversationWindow.ts +78 -0
  32. package/src/flows/FlowMapCanvas.tsx +2 -2
  33. package/src/hooks/useConversationDocuments.ts +4 -2
  34. package/src/index.ts +73 -4
  35. package/src/lib/cn.ts +15 -0
  36. package/src/lib/phone.ts +34 -0
  37. package/src/preview/ConversationPreview.tsx +148 -0
  38. package/src/preview/createMockConversationsApi.ts +111 -0
  39. package/src/preview/createMockSSEProvider.ts +40 -0
  40. package/src/preview/createPreviewWebhookClient.test.ts +105 -0
  41. package/src/preview/createPreviewWebhookClient.ts +99 -0
  42. package/src/preview/index.ts +40 -0
  43. package/src/preview/mockEventSource.ts +53 -0
  44. package/src/preview/preview.test.ts +175 -0
  45. package/src/preview/previewFixtures.ts +153 -0
  46. package/src/preview/previewStore.ts +193 -0
  47. package/src/preview/startPreviewScript.ts +60 -0
  48. package/src/providers/types.ts +36 -2
  49. package/src/settings/WhatsAppTemplatesSettings.tsx +106 -0
  50. package/src/styles.css +136 -0
  51. package/src/types.ts +8 -0
  52. package/src/useDarkMode.ts +26 -0
  53. package/src/useIsNarrow.ts +29 -0
@@ -0,0 +1,56 @@
1
+ // src/useDarkMode.ts
2
+ import { useState, useEffect, useCallback } from "react";
3
+ var STORAGE_KEY = "conversations-ui-dark-mode";
4
+ function getInitialDark() {
5
+ if (typeof window === "undefined") return false;
6
+ const stored = localStorage.getItem(STORAGE_KEY);
7
+ if (stored !== null) {
8
+ return stored === "true";
9
+ }
10
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
11
+ }
12
+ function useIsDarkTheme() {
13
+ const [isDark, setIsDark] = useState(
14
+ () => typeof document !== "undefined" && document.documentElement.classList.contains("dark")
15
+ );
16
+ useEffect(() => {
17
+ const root = document.documentElement;
18
+ const observer = new MutationObserver(() => setIsDark(root.classList.contains("dark")));
19
+ observer.observe(root, { attributes: true, attributeFilter: ["class"] });
20
+ setIsDark(root.classList.contains("dark"));
21
+ return () => observer.disconnect();
22
+ }, []);
23
+ return isDark;
24
+ }
25
+ function useDarkMode() {
26
+ const [isDark, setIsDark] = useState(getInitialDark);
27
+ useEffect(() => {
28
+ const root = document.documentElement;
29
+ if (isDark) {
30
+ root.classList.add("dark");
31
+ } else {
32
+ root.classList.remove("dark");
33
+ }
34
+ localStorage.setItem(STORAGE_KEY, String(isDark));
35
+ }, [isDark]);
36
+ useEffect(() => {
37
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
38
+ const handleChange = (event) => {
39
+ const stored = localStorage.getItem(STORAGE_KEY);
40
+ if (stored === null) {
41
+ setIsDark(event.matches);
42
+ }
43
+ };
44
+ mediaQuery.addEventListener("change", handleChange);
45
+ return () => mediaQuery.removeEventListener("change", handleChange);
46
+ }, []);
47
+ const toggle = useCallback(() => {
48
+ setIsDark((prev) => !prev);
49
+ }, []);
50
+ return { isDark, toggle };
51
+ }
52
+
53
+ export {
54
+ useIsDarkTheme,
55
+ useDarkMode
56
+ };
@@ -151,49 +151,9 @@ function waToHTMLInline(text) {
151
151
  return escapeHtml(text).replace(/\*([^*\n]+)\*/g, "<strong>$1</strong>").replace(/_([^_\n]+)_/g, "<em>$1</em>").replace(/~([^~\n]+)~/g, "<del>$1</del>").replace(/`([^`\n]+)`/g, "<code>$1</code>");
152
152
  }
153
153
 
154
- // src/useDarkMode.ts
155
- import { useState, useEffect, useCallback } from "react";
156
- var STORAGE_KEY = "conversations-ui-dark-mode";
157
- function getInitialDark() {
158
- if (typeof window === "undefined") return false;
159
- const stored = localStorage.getItem(STORAGE_KEY);
160
- if (stored !== null) {
161
- return stored === "true";
162
- }
163
- return window.matchMedia("(prefers-color-scheme: dark)").matches;
164
- }
165
- function useDarkMode() {
166
- const [isDark, setIsDark] = useState(getInitialDark);
167
- useEffect(() => {
168
- const root = document.documentElement;
169
- if (isDark) {
170
- root.classList.add("dark");
171
- } else {
172
- root.classList.remove("dark");
173
- }
174
- localStorage.setItem(STORAGE_KEY, String(isDark));
175
- }, [isDark]);
176
- useEffect(() => {
177
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
178
- const handleChange = (event) => {
179
- const stored = localStorage.getItem(STORAGE_KEY);
180
- if (stored === null) {
181
- setIsDark(event.matches);
182
- }
183
- };
184
- mediaQuery.addEventListener("change", handleChange);
185
- return () => mediaQuery.removeEventListener("change", handleChange);
186
- }, []);
187
- const toggle = useCallback(() => {
188
- setIsDark((prev) => !prev);
189
- }, []);
190
- return { isDark, toggle };
191
- }
192
-
193
154
  export {
194
155
  parseWhatsAppFormatting,
195
156
  waToHTML,
196
157
  htmlToWA,
197
- waToHTMLInline,
198
- useDarkMode
158
+ waToHTMLInline
199
159
  };
@@ -1,7 +1,9 @@
1
1
  import {
2
- parseWhatsAppFormatting,
3
- useDarkMode
4
- } from "../chunk-ZDURDZTM.js";
2
+ useIsDarkTheme
3
+ } from "../chunk-NV2RZ5KT.js";
4
+ import {
5
+ parseWhatsAppFormatting
6
+ } from "../chunk-OGRRHQQW.js";
5
7
 
6
8
  // src/flows/FlowNodeCard.tsx
7
9
  import { Handle, Position } from "@xyflow/react";
@@ -524,7 +526,7 @@ var BACKGROUND_COLOR_LIGHT = "#cbd5e1";
524
526
  var BACKGROUND_COLOR_DARK = "#334155";
525
527
  function FlowMapCanvas({ graphs, rootKey, onOpenFlow, labels: labelsOverride }) {
526
528
  const labels = { ...DEFAULT_FLOW_EDITOR_LABELS, ...labelsOverride };
527
- const { isDark } = useDarkMode();
529
+ const isDark = useIsDarkTheme();
528
530
  const positions = useMemo(() => computeFlowMapLayout(graphs, rootKey), [graphs, rootKey]);
529
531
  const nodes = useMemo(
530
532
  () => Object.values(graphs).map((g) => ({
package/dist/index.d.ts CHANGED
@@ -1,47 +1,7 @@
1
1
  import * as react from 'react';
2
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
- }
3
+ import { M as MessagePayload, k as ConversationsFeatures, i as ConversationSummary, f as ConversationChannel, j as ConversationsApi, S as SSEProvider, g as ConversationDocument } from './types-C0PtaO7S.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, h as ConversationEventSource, l as ConversationsTheme, m as ConversationsUIConfig, D as DEFAULT_CONVERSATION_CHANNEL, F as FormatContactHandleParams, H as HANDLE_KIND, n as HandleKind, R as REOPEN_MECHANISM, o as ReopenMechanism, p as capabilitiesOf, q as channelFiltersFor, r as contactFlag, s as formatContactHandle } from './types-C0PtaO7S.js';
45
5
 
46
6
  type ResolveMediaUrl = (message: MessagePayload) => Promise<string | null>;
47
7
  interface MediaRendererProps {
@@ -60,8 +20,9 @@ interface MessageBubbleProps {
60
20
  isSelected?: boolean;
61
21
  onToggleSelect?: () => void;
62
22
  onResolveMediaUrl?: ResolveMediaUrl;
23
+ className?: string;
63
24
  }
64
- declare function MessageBubble({ message, isMine, senderName, isFirstInGroup, isSelecting, isSelected, onToggleSelect, onResolveMediaUrl, }: MessageBubbleProps): react.JSX.Element;
25
+ declare function MessageBubble({ message, isMine, senderName, isFirstInGroup, isSelecting, isSelected, onToggleSelect, onResolveMediaUrl, className, }: MessageBubbleProps): react.JSX.Element;
65
26
 
66
27
  interface ConversationWallpaperProps {
67
28
  children?: ReactNode;
@@ -82,6 +43,14 @@ interface ConversationLocales {
82
43
  viewImage: string;
83
44
  listenAudio: string;
84
45
  viewVideo: string;
46
+ moderationFlagged: string;
47
+ mediaLoading: string;
48
+ mediaRetry: string;
49
+ mediaError: string;
50
+ mediaUnavailable: string;
51
+ imageAlt: string;
52
+ untitledDocument: string;
53
+ downloadFile: string;
85
54
  };
86
55
  selection: {
87
56
  select: string;
@@ -123,8 +92,14 @@ interface MessageComposerProps {
123
92
  maxLength?: number;
124
93
  disabled?: boolean;
125
94
  acceptedFileTypes?: string;
95
+ className?: string;
96
+ classNames?: Partial<MessageComposerClassNames>;
97
+ }
98
+ interface MessageComposerClassNames {
99
+ root: string;
100
+ field: string;
126
101
  }
127
- declare const MessageComposer: ({ onSend, onAttach, value: externalValue, onChange: externalOnChange, features, placeholder, maxLength, disabled, acceptedFileTypes, }: MessageComposerProps) => react.JSX.Element;
102
+ declare const MessageComposer: ({ onSend, onAttach, value: externalValue, onChange: externalOnChange, features, placeholder, maxLength, disabled, acceptedFileTypes, className, classNames, }: MessageComposerProps) => react.JSX.Element;
128
103
 
129
104
  interface WhatsAppMessageEditorProps {
130
105
  value: string;
@@ -145,10 +120,16 @@ interface SimpleEmojiPickerProps {
145
120
  }
146
121
  declare function SimpleEmojiPicker({ onSelect, label, pickerWidth, pickerMaxHeight }: SimpleEmojiPickerProps): react.JSX.Element;
147
122
 
123
+ interface DateDividerClassNames {
124
+ root: string;
125
+ label: string;
126
+ }
148
127
  interface DateDividerProps {
149
128
  iso: string;
129
+ className?: string;
130
+ classNames?: Partial<DateDividerClassNames>;
150
131
  }
151
- declare function DateDivider({ iso }: DateDividerProps): react.JSX.Element;
132
+ declare function DateDivider({ iso, className, classNames }: DateDividerProps): react.JSX.Element;
152
133
 
153
134
  interface AvatarProps {
154
135
  name?: string | null;
@@ -158,76 +139,25 @@ interface AvatarProps {
158
139
  }
159
140
  declare function Avatar({ name, avatarUrl, size, className }: AvatarProps): react.JSX.Element;
160
141
 
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
- }
222
-
223
142
  interface ConversationListItemProps {
224
143
  conversation: ConversationSummary;
225
144
  active?: boolean;
226
145
  selected?: boolean;
227
146
  onClick?: () => void;
228
147
  onSelect?: (id: string) => void;
229
- }
230
- declare const ConversationListItem: ({ conversation, active, selected, onClick, onSelect, }: ConversationListItemProps) => react.JSX.Element;
148
+ /**
149
+ * Desliga a borda inferior quando o item é composto dentro de outra linha (ver `ConversationRow`):
150
+ * com ela ligada, a borda corta a própria linha ao meio, separando o item do rodapé de status.
151
+ */
152
+ showDivider?: boolean;
153
+ /**
154
+ * Desliga o fundo de selecionado. Par do `showDivider`: quando o item é composto dentro de uma
155
+ * linha maior, quem pinta o realce é a linha — senão só o bloco do item fica cinza e o resto
156
+ * (checkbox, pills, barra lateral) continua branco, como se metade da linha estivesse selecionada.
157
+ */
158
+ highlightActive?: boolean;
159
+ }
160
+ declare const ConversationListItem: ({ conversation, active, selected, onClick, onSelect, showDivider, highlightActive, }: ConversationListItemProps) => react.JSX.Element;
231
161
 
232
162
  type ToastType = 'success' | 'error' | 'info';
233
163
  interface ToastContextValue {
@@ -279,11 +209,258 @@ interface MessageTailProps {
279
209
  }
280
210
  declare function MessageTail({ isOutbound }: MessageTailProps): react.JSX.Element;
281
211
 
212
+ /**
213
+ * Janela de sessão: o intervalo em que o canal aceita mensagem livre do atendente. No WhatsApp são
214
+ * 24h desde o último contato do cliente; fora dela só template. Cada canal tem a sua regra — e há
215
+ * canal sem janela nenhuma — então a política vem de `capabilitiesOf`, não de constante fixa.
216
+ *
217
+ * Mora no SDK porque é regra de plataforma, não de produto: todo projeto que usa este pacote
218
+ * precisa dela para saber o que o atendente ainda consegue fazer.
219
+ */
220
+
221
+ declare const CONVERSATION_WINDOW: {
222
+ readonly ALL: "all";
223
+ readonly FRESH: "fresh";
224
+ readonly WARNING: "warning";
225
+ readonly CRITICAL: "critical";
226
+ readonly EXPIRED: "expired";
227
+ };
228
+ type ConversationWindow = (typeof CONVERSATION_WINDOW)[keyof typeof CONVERSATION_WINDOW];
229
+ declare const WINDOW_FILTERS: readonly [{
230
+ readonly value: "all";
231
+ readonly label: "Todas";
232
+ readonly dotClass: "";
233
+ }, {
234
+ readonly value: "fresh";
235
+ readonly label: "<12h";
236
+ readonly dotClass: "bg-green-500";
237
+ }, {
238
+ readonly value: "warning";
239
+ readonly label: "12-21h";
240
+ readonly dotClass: "bg-yellow-500";
241
+ }, {
242
+ readonly value: "critical";
243
+ readonly label: "21-24h";
244
+ readonly dotClass: "bg-red-500";
245
+ }, {
246
+ readonly value: "expired";
247
+ readonly label: ">24h";
248
+ readonly dotClass: "bg-gray-400";
249
+ }];
250
+ type WindowOfParams = {
251
+ readonly lastInboundAt: string | null;
252
+ readonly now: number;
253
+ /** Ausente = `whatsapp`. */
254
+ readonly channel?: ConversationChannel | undefined;
255
+ };
256
+ /**
257
+ * Sem `lastInboundAt` o cliente nunca escreveu, então não há janela aberta — classificar como
258
+ * expirada é o comportamento seguro: evita o atendente tentar texto livre e receber recusa.
259
+ *
260
+ * Canal sem janela de sessão (chat de site) é sempre `fresh`: ali nada expira, e marcar expirado
261
+ * bloquearia o composer inventando um limite que a plataforma não impõe.
262
+ */
263
+ declare function windowOf(params: WindowOfParams): ConversationWindow;
264
+ declare function formatStalledFor(lastAt: string, now: number): string;
265
+
266
+ type ConversationRowClassNames = {
267
+ root: string;
268
+ windowBar: string;
269
+ };
270
+ type ConversationRowProps = {
271
+ conversation: ConversationSummary;
272
+ active: boolean;
273
+ selected: boolean;
274
+ now: number;
275
+ busy: boolean;
276
+ onOpen: () => void;
277
+ onToggleSelected: () => void;
278
+ onTakeover: () => void;
279
+ className?: string;
280
+ classNames?: Partial<ConversationRowClassNames>;
281
+ };
282
+ declare function ConversationRow({ conversation, active, selected, now, busy, onOpen, onToggleSelected, onTakeover, className, classNames, }: ConversationRowProps): react.JSX.Element;
283
+
284
+ declare const CHANNEL_BRAND_COLOR: Readonly<Record<ConversationChannel, string>>;
285
+ interface ChannelIconProps {
286
+ channel?: ConversationChannel | undefined;
287
+ size?: number;
288
+ className?: string;
289
+ }
290
+ declare function ChannelIcon({ channel, size, className }: ChannelIconProps): react.JSX.Element;
291
+
292
+ interface ConversationHeaderLabels {
293
+ botMode: string;
294
+ humanMode: string;
295
+ returnToBot: string;
296
+ finish: string;
297
+ takeover: string;
298
+ download: string;
299
+ documents: string;
300
+ back: string;
301
+ moreActions: string;
302
+ }
303
+ declare const DEFAULT_CONVERSATION_HEADER_LABELS: ConversationHeaderLabels;
304
+ /**
305
+ * Partes estilizáveis do cabeçalho. Cada chave recebe classes que o `cn` funde por cima da base, e
306
+ * conflito de utilitário (padding, gap, borda) fica com o valor do produto.
307
+ */
308
+ interface ConversationHeaderClassNames {
309
+ root: string;
310
+ identity: string;
311
+ name: string;
312
+ meta: string;
313
+ actions: string;
314
+ desktopActions: string;
315
+ mobileMenu: string;
316
+ }
317
+ interface ConversationHeaderProps {
318
+ conversation: ConversationSummary;
319
+ busy?: boolean;
320
+ onTakeover?: () => void;
321
+ onReturnToBot?: () => void;
322
+ onFinish?: () => void;
323
+ onDownload?: () => void;
324
+ onOpenDocuments?: () => void;
325
+ documentsOpen?: boolean;
326
+ onBack?: () => void;
327
+ labels?: Partial<ConversationHeaderLabels>;
328
+ className?: string;
329
+ classNames?: Partial<ConversationHeaderClassNames>;
330
+ }
331
+ declare function ConversationHeader({ conversation, busy, onTakeover, onReturnToBot, onFinish, onDownload, onOpenDocuments, documentsOpen, onBack, labels: labelsOverride, className, classNames, }: ConversationHeaderProps): react.JSX.Element;
332
+
333
+ /**
334
+ * "Suas Seleções": o que o bot já coletou na conversa. Para o atendente que assume no meio, é a
335
+ * diferença entre ler o transcript inteiro e ver o estado em duas linhas.
336
+ *
337
+ * O pacote não interpreta o contexto — ele é `Record<string, unknown>` e cada produto nomeia as
338
+ * próprias chaves. O host traduz para `entries`; aqui só se decide como mostrar.
339
+ */
340
+ interface ConversationContextEntry {
341
+ key: string;
342
+ label: string;
343
+ value?: string | undefined;
344
+ icon?: string;
345
+ }
346
+ interface ConversationContextPanelLabels {
347
+ title: string;
348
+ empty: string;
349
+ }
350
+ declare const DEFAULT_CONVERSATION_CONTEXT_LABELS: ConversationContextPanelLabels;
351
+ interface ConversationContextPanelClassNames {
352
+ root: string;
353
+ toggle: string;
354
+ counter: string;
355
+ body: string;
356
+ }
357
+ interface ConversationContextPanelProps {
358
+ entries: readonly ConversationContextEntry[];
359
+ labels?: Partial<ConversationContextPanelLabels>;
360
+ className?: string;
361
+ classNames?: Partial<ConversationContextPanelClassNames>;
362
+ }
363
+ declare function ConversationContextPanel({ entries, labels: labelsOverride, className, classNames, }: ConversationContextPanelProps): react.JSX.Element;
364
+
365
+ interface WindowExpiredNoticeLabels {
366
+ title: string;
367
+ description: string;
368
+ sendTemplate: string;
369
+ }
370
+ declare const DEFAULT_WINDOW_EXPIRED_LABELS: WindowExpiredNoticeLabels;
371
+ interface WindowExpiredNoticeProps {
372
+ onSendTemplate?: () => void;
373
+ disabled?: boolean;
374
+ labels?: Partial<WindowExpiredNoticeLabels>;
375
+ className?: string;
376
+ }
377
+ declare function WindowExpiredNotice({ onSendTemplate, disabled, labels: labelsOverride, className, }: WindowExpiredNoticeProps): react.JSX.Element;
378
+ /**
379
+ * Só a faixa `expired` bloqueia: 21-24h ainda aceita texto livre e merece alerta, não impedimento.
380
+ */
381
+ declare function isWindowBlocking(window: ConversationWindow): boolean;
382
+
383
+ /**
384
+ * Arquivos trocados na conversa. Sai do transcript e vira lista própria porque anexo é o que o
385
+ * atendente mais precisa reencontrar depois — rolar meses de mensagens para achar um comprovante é
386
+ * o caso que a busca por documento existe para eliminar.
387
+ *
388
+ * Usa `useConversationDocuments`, então funciona com qualquer `ConversationsApi`. Host sem
389
+ * biblioteca de documentos cai no estado vazio, sem quebrar.
390
+ */
391
+ interface ConversationDocumentsPanelLabels {
392
+ toggle: string;
393
+ title: string;
394
+ searchPlaceholder: string;
395
+ empty: string;
396
+ loading: string;
397
+ failure: string;
398
+ download: string;
399
+ }
400
+ declare const DEFAULT_CONVERSATION_DOCUMENTS_LABELS: ConversationDocumentsPanelLabels;
401
+ interface ConversationDocumentsPanelClassNames {
402
+ root: string;
403
+ body: string;
404
+ }
405
+ interface ConversationDocumentsPanelProps {
406
+ conversationId: string;
407
+ /** Controlado de fora porque o gatilho vive no cabeçalho, junto das outras ações da conversa. */
408
+ open: boolean;
409
+ labels?: Partial<ConversationDocumentsPanelLabels>;
410
+ className?: string;
411
+ classNames?: Partial<ConversationDocumentsPanelClassNames>;
412
+ }
413
+ declare function ConversationDocumentsPanel({ conversationId, open, labels: labelsOverride, className, classNames, }: ConversationDocumentsPanelProps): react.JSX.Element | null;
414
+
415
+ /**
416
+ * Serialização do transcript para download. Fica no pacote porque o formato de um histórico de
417
+ * WhatsApp legível não é regra de negócio de ninguém — e porque o host que já tem as mensagens em
418
+ * tela não deveria precisar de rota nova só para salvar um arquivo.
419
+ *
420
+ * Funções puras, separadas do disparo do download: é o que permite testá-las sem DOM.
421
+ */
422
+
423
+ type BuildTranscriptTextParams = {
424
+ readonly messages: readonly MessagePayload[];
425
+ readonly whatsappNumber: string;
426
+ readonly clientName?: string | undefined;
427
+ };
428
+ /**
429
+ * Formato próximo ao export nativo do WhatsApp (`[data hora] Autor: texto`), que é o que pessoas e
430
+ * ferramentas de suporte já sabem ler.
431
+ */
432
+ declare function buildTranscriptText(params: BuildTranscriptTextParams): string;
433
+ declare function buildTranscriptFilename(whatsappNumber: string, generatedAt: Date): string;
434
+ /**
435
+ * Dispara o download no navegador. `revokeObjectURL` no fim não é higiene opcional: sem ele cada
436
+ * export retém o blob inteiro em memória até a aba fechar.
437
+ */
438
+ declare function downloadTextFile(filename: string, content: string): void;
439
+
440
+ /**
441
+ * Leitura passiva do tema do host: observa a classe `dark` no `<html>` e não escreve nada.
442
+ *
443
+ * Existe porque `useDarkMode` é um controlador — ele grava a classe e persiste a preferência. Um
444
+ * componente que só precisa escolher cor não pode usá-lo: bastava renderizar o mapa de fluxos
445
+ * para o app inteiro do host trocar de tema, seguindo o `prefers-color-scheme` do sistema em vez
446
+ * da configuração da aplicação.
447
+ */
448
+ declare function useIsDarkTheme(): boolean;
282
449
  declare function useDarkMode(): {
283
450
  isDark: boolean;
284
451
  toggle: () => void;
285
452
  };
286
453
 
454
+ /**
455
+ * Detecta tela estreita para decisões que CSS não resolve — como abrir ou não um painel por padrão.
456
+ *
457
+ * O breakpoint é o mesmo das classes `cv-only-*` e `.cv-back` (1024px). Duplicado aqui porque
458
+ * JavaScript não lê media query do stylesheet; se um dia divergirem, o sintoma é painel abrindo
459
+ * numa largura onde o resto da UI já mudou de modo.
460
+ */
461
+ declare const NARROW_MAX_WIDTH_PX = 1023;
462
+ declare function useIsNarrow(): boolean;
463
+
287
464
  interface UseWaitingNotificationsResult {
288
465
  unreadCount: number;
289
466
  conversations: ConversationSummary[];
@@ -435,6 +612,41 @@ interface WelcomeFarewellFormProps {
435
612
  }
436
613
  declare function WelcomeFarewellForm({ welcomeMessage, onWelcomeMessageChange, farewellMessage, onFarewellMessageChange, onSave, saving, saveSuccess, welcomePlaceholders, farewellPlaceholders, labels: labelsOverride, }: WelcomeFarewellFormProps): react.JSX.Element;
437
614
 
615
+ declare const TEMPLATE_SETTINGS_TAB: {
616
+ readonly SELECT: "select";
617
+ readonly CREATE: "create";
618
+ };
619
+ type TemplateSettingsTab = (typeof TEMPLATE_SETTINGS_TAB)[keyof typeof TEMPLATE_SETTINGS_TAB];
620
+ interface WhatsAppTemplatesSettingsLabels {
621
+ selectTab: string;
622
+ createTab: string;
623
+ }
624
+ declare const DEFAULT_TEMPLATES_SETTINGS_LABELS: WhatsAppTemplatesSettingsLabels;
625
+ interface WhatsAppTemplatesSettingsProps {
626
+ templates: WhatsAppTemplateSummary[];
627
+ loadingTemplates?: boolean;
628
+ templatesError?: boolean;
629
+ onRefreshTemplates?: () => void;
630
+ selectedTemplateName: string;
631
+ onSelectTemplate: (name: string, template: WhatsAppTemplateSummary | undefined) => void;
632
+ variables: string[];
633
+ onVariablesChange: (variables: string[]) => void;
634
+ availableVariables?: WhatsAppTemplateVariableSuggestion[];
635
+ saving?: boolean;
636
+ saveSuccess?: boolean;
637
+ onSave: (event: FormEvent) => void;
638
+ /** Ausente = host não sabe criar template; a aba de criação nem aparece. */
639
+ create?: {
640
+ value: WhatsAppCreateTemplateState;
641
+ onChange: (value: WhatsAppCreateTemplateState) => void;
642
+ onSubmit: (event: FormEvent) => void;
643
+ submitting?: boolean;
644
+ result?: WhatsAppCreateTemplateResult | null;
645
+ };
646
+ labels?: Partial<WhatsAppTemplatesSettingsLabels>;
647
+ }
648
+ declare function WhatsAppTemplatesSettings({ labels: labelsOverride, create, ...settingsProps }: WhatsAppTemplatesSettingsProps): react.JSX.Element;
649
+
438
650
  interface TopicItem {
439
651
  key: string;
440
652
  label: string;
@@ -515,7 +727,7 @@ interface UseConversationDocumentsResult {
515
727
  error: Error | undefined;
516
728
  refetch: () => Promise<void>;
517
729
  }
518
- declare function useConversationDocuments(conversationId: string, params?: UseConversationDocumentsParams): UseConversationDocumentsResult;
730
+ declare function useConversationDocuments(conversationId: string | undefined, params?: UseConversationDocumentsParams): UseConversationDocumentsResult;
519
731
 
520
732
  type ConversationRealtimeHandler = (event: MessageEvent) => void;
521
733
  declare function useConversationRealtime(conversationId: string | undefined, onEvent: ConversationRealtimeHandler): void;
@@ -539,4 +751,4 @@ interface AsyncResourceState<T> {
539
751
  refetch: () => Promise<void>;
540
752
  }
541
753
 
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 };
754
+ export { type AsyncResourceState, AudioPlayer, type AudioPlayerProps, Avatar, 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, ConversationListItem, type ConversationListItemProps, type ConversationLocales, ConversationLocalesProvider, type ConversationLocalesProviderProps, type ConversationRealtimeHandler, ConversationRow, type ConversationRowClassNames, type ConversationRowProps, ConversationSummary, ConversationWallpaper, type ConversationWallpaperProps, type ConversationWindow, ConversationsApi, ConversationsFeatures, ConversationsProvider, DEFAULT_CONVERSATION_CONTEXT_LABELS, DEFAULT_CONVERSATION_DOCUMENTS_LABELS, DEFAULT_CONVERSATION_HEADER_LABELS, DEFAULT_TEMPLATES_SETTINGS_LABELS, DEFAULT_WINDOW_EXPIRED_LABELS, DateDivider, type DateDividerClassNames, type DateDividerProps, EmojiPicker, type EmojiPickerProps, FileIcon, type FileIconProps, Lightbox, type LightboxProps, MediaRenderer, type MediaRendererProps, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerClassNames, type MessageComposerProps, MessagePayload, MessageTail, MessageText, type MessageTextProps, MessageTimestamp, NARROW_MAX_WIDTH_PX, type ResolveMediaUrl, SSEProvider, SimpleEmojiPicker, type SimpleEmojiPickerProps, StatusTicks, type StatusTicksProps, TEMPLATE_SETTINGS_TAB, type TemplateSettingsTab, ToastProvider, type TopicItem, TopicsForm, type TopicsFormLabels, type TopicsFormProps, type UseConversationContextResult, type UseConversationDocumentsParams, type UseConversationDocumentsResult, type UseConversationListParams, type UseConversationListResult, type UseConversationMessagesResult, WINDOW_FILTERS, 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, WhatsAppTemplatesSettings, type WhatsAppTemplatesSettingsLabels, type WhatsAppTemplatesSettingsProps, WindowExpiredNotice, type WindowExpiredNoticeLabels, type WindowExpiredNoticeProps, type WindowOfParams, buildTranscriptFilename, buildTranscriptText, downloadTextFile, formatFileSize, formatPhone, formatStalledFor, formatTimestamp, htmlToWA, isWindowBlocking, parseWhatsAppFormatting, phoneInitials, toast, useConversationContext, useConversationDocuments, useConversationList, useConversationLocales, useConversationMessages, useConversationRealtime, useConversations, useDarkMode, useGlobalRealtime, useIsDarkTheme, useIsNarrow, useToast, useWaitingNotifications, waToHTML, waToHTMLInline, windowOf };