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

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 (99) hide show
  1. package/dist/{chunk-ZDURDZTM.js → chunk-2AYDBWNE.js} +14 -0
  2. package/dist/chunk-OIDAIVCH.js +1985 -0
  3. package/dist/flows/index.d.ts +14 -2
  4. package/dist/flows/index.js +117 -39
  5. package/dist/index.d.ts +729 -136
  6. package/dist/index.js +1418 -1104
  7. package/dist/preview/index.d.ts +373 -0
  8. package/dist/preview/index.js +1247 -0
  9. package/dist/styles.css +198 -0
  10. package/dist/types-O7kMP1Yn.d.ts +416 -0
  11. package/package.json +10 -3
  12. package/src/AudioRecorderButton.test.tsx +30 -0
  13. package/src/AudioRecorderButton.tsx +248 -0
  14. package/src/Avatar.tsx +30 -4
  15. package/src/ChannelIcon.tsx +87 -0
  16. package/src/ConversationContextPanel.tsx +127 -0
  17. package/src/ConversationDocumentsPanel.tsx +425 -0
  18. package/src/ConversationHeader.tsx +257 -0
  19. package/src/ConversationListItem.tsx +54 -7
  20. package/src/ConversationLocalesProvider.tsx +16 -0
  21. package/src/ConversationRow.tsx +137 -0
  22. package/src/DateDivider.tsx +16 -3
  23. package/src/DocumentsLibrary.tsx +322 -0
  24. package/src/EmojiPicker.tsx +69 -55
  25. package/src/FileIcon.test.ts +83 -0
  26. package/src/FileIcon.tsx +88 -11
  27. package/src/InteractiveMessage.test.tsx +41 -0
  28. package/src/InteractiveMessage.tsx +143 -0
  29. package/src/Lightbox.tsx +18 -3
  30. package/src/MediaRenderer.tsx +64 -21
  31. package/src/MessageBubble.tsx +54 -5
  32. package/src/MessageComposer.test.tsx +35 -0
  33. package/src/MessageComposer.tsx +146 -19
  34. package/src/RichMessageComposer.test.tsx +83 -0
  35. package/src/RichMessageComposer.tsx +380 -0
  36. package/src/Wallpaper.test.tsx +21 -0
  37. package/src/Wallpaper.tsx +54 -6
  38. package/src/WhatsAppMessageEditor.tsx +28 -4
  39. package/src/WindowExpiredNotice.tsx +57 -0
  40. package/src/audioRecorderFormat.test.ts +67 -0
  41. package/src/conversationChannel.test.ts +53 -0
  42. package/src/conversationChannel.ts +146 -0
  43. package/src/conversationTranscript.test.ts +65 -0
  44. package/src/conversationTranscript.ts +64 -0
  45. package/src/conversationWindow.test.ts +90 -0
  46. package/src/conversationWindow.ts +78 -0
  47. package/src/documentTypeLabel.test.ts +57 -0
  48. package/src/emojiCatalog.test.ts +35 -0
  49. package/src/emojiCatalog.ts +189 -0
  50. package/src/flows/FlowGroupHeader.tsx +12 -2
  51. package/src/flows/FlowMapCanvas.tsx +17 -14
  52. package/src/flows/FlowMapNode.tsx +3 -1
  53. package/src/flows/FlowNodeCard.tsx +22 -4
  54. package/src/flows/FlowNodePanel.tsx +132 -35
  55. package/src/flows/FlowPalette.tsx +6 -2
  56. package/src/flows/FlowWhatsAppPreview.tsx +14 -3
  57. package/src/flows/flowGraph.ts +5 -5
  58. package/src/flows/labels.ts +5 -0
  59. package/src/hooks/useConversationActions.ts +56 -0
  60. package/src/hooks/useConversationDocuments.ts +15 -9
  61. package/src/hooks/useConversationList.ts +15 -9
  62. package/src/hooks/useConversationMessages.ts +2 -2
  63. package/src/index.ts +129 -16
  64. package/src/lib/cn.test.ts +29 -0
  65. package/src/lib/cn.ts +15 -0
  66. package/src/lib/createMediaUrlResolver.ts +33 -0
  67. package/src/lib/paginated.test.ts +33 -0
  68. package/src/lib/paginated.ts +26 -0
  69. package/src/lib/phone.ts +34 -0
  70. package/src/preview/ConversationPreview.tsx +325 -0
  71. package/src/preview/MediaTypesPreview.tsx +87 -0
  72. package/src/preview/conversationPreviewFailures.test.ts +64 -0
  73. package/src/preview/createMockConversationsApi.ts +271 -0
  74. package/src/preview/createMockSSEProvider.ts +40 -0
  75. package/src/preview/createPreviewBridgeClient.test.ts +92 -0
  76. package/src/preview/createPreviewBridgeClient.ts +88 -0
  77. package/src/preview/createPreviewWebhookClient.test.ts +105 -0
  78. package/src/preview/createPreviewWebhookClient.ts +131 -0
  79. package/src/preview/index.ts +61 -0
  80. package/src/preview/mediaTypeOf.test.ts +15 -0
  81. package/src/preview/mockDocumentsSearch.test.ts +57 -0
  82. package/src/preview/mockEventSource.ts +53 -0
  83. package/src/preview/preview.test.ts +177 -0
  84. package/src/preview/previewFileSamples.test.ts +151 -0
  85. package/src/preview/previewFileSamples.ts +74 -0
  86. package/src/preview/previewFixtures.ts +440 -0
  87. package/src/preview/previewMediaSource.test.ts +62 -0
  88. package/src/preview/previewMediaSource.ts +91 -0
  89. package/src/preview/previewStore.ts +193 -0
  90. package/src/preview/startPreviewScript.ts +60 -0
  91. package/src/providers/types.ts +163 -11
  92. package/src/quickReply.test.ts +58 -0
  93. package/src/settings/WhatsAppCreateTemplateForm.tsx +3 -1
  94. package/src/settings/WhatsAppTemplatesSettings.tsx +106 -0
  95. package/src/styles.css +136 -0
  96. package/src/types.ts +46 -1
  97. package/src/useDarkMode.ts +26 -0
  98. package/src/useIsNarrow.ts +29 -0
  99. package/src/useWaitingNotifications.ts +74 -29
@@ -0,0 +1,1985 @@
1
+ import {
2
+ parseWhatsAppFormatting,
3
+ useIsDarkTheme
4
+ } from "./chunk-2AYDBWNE.js";
5
+
6
+ // src/ConversationLocalesProvider.tsx
7
+ import { createContext, useContext } from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ var DEFAULT_LOCALES = {
10
+ bubble: {
11
+ customer: "Cliente",
12
+ bot: "Bot",
13
+ agent: "Atendente",
14
+ document: "documento",
15
+ media: "m\xEDdia",
16
+ templateLabel: "Template",
17
+ readAt: "Lida \xE0s ",
18
+ windowExpired: "Janela de 24h expirada",
19
+ viewImage: "Ver imagem",
20
+ listenAudio: "Ouvir \xE1udio",
21
+ viewVideo: "Ver v\xEDdeo",
22
+ moderationFlagged: "Linguagem ofensiva",
23
+ mediaLoading: "Carregando...",
24
+ mediaRetry: "Erro \u2014 tentar novamente",
25
+ mediaError: "Erro",
26
+ mediaUnavailable: "M\xEDdia indispon\xEDvel",
27
+ imageAlt: "Imagem",
28
+ untitledDocument: "Documento",
29
+ downloadFile: "Baixar"
30
+ },
31
+ selection: {
32
+ select: "Selecionar"
33
+ },
34
+ dateDivider: {
35
+ today: "Hoje",
36
+ yesterday: "Ontem"
37
+ }
38
+ };
39
+ var ConversationLocalesContext = createContext(DEFAULT_LOCALES);
40
+ function ConversationLocalesProvider({ children, locales }) {
41
+ const merged = {
42
+ bubble: { ...DEFAULT_LOCALES.bubble, ...locales?.bubble },
43
+ selection: { ...DEFAULT_LOCALES.selection, ...locales?.selection },
44
+ dateDivider: { ...DEFAULT_LOCALES.dateDivider, ...locales?.dateDivider }
45
+ };
46
+ return /* @__PURE__ */ jsx(ConversationLocalesContext.Provider, { value: merged, children });
47
+ }
48
+ function useConversationLocales() {
49
+ return useContext(ConversationLocalesContext);
50
+ }
51
+
52
+ // src/StatusTicks.tsx
53
+ import { AlertTriangle } from "lucide-react";
54
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
55
+ var STATUS_COLOR_CLASS = {
56
+ sent: "text-black/40 dark:text-white/40",
57
+ delivered: "text-black/40 dark:text-white/40",
58
+ read: "text-sky-500",
59
+ failed: "text-red-500"
60
+ };
61
+ function Ticks({ double }) {
62
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 20 12", width: "15", height: "9", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
63
+ double && /* @__PURE__ */ jsx2("path", { d: "M1 6.5L4.5 10L11 2", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }),
64
+ /* @__PURE__ */ jsx2(
65
+ "path",
66
+ {
67
+ d: double ? "M6 6.5L9.5 10L19 1" : "M1 6.5L5 10.5L14.5 1",
68
+ stroke: "currentColor",
69
+ strokeWidth: "1.6",
70
+ strokeLinecap: "round",
71
+ strokeLinejoin: "round"
72
+ }
73
+ )
74
+ ] });
75
+ }
76
+ function StatusTicks({ status, title }) {
77
+ const colorClass = STATUS_COLOR_CLASS[status] ?? STATUS_COLOR_CLASS.sent;
78
+ return /* @__PURE__ */ jsx2("span", { className: `cursor-help leading-none flex items-center ${colorClass}`, title, children: status === "failed" ? /* @__PURE__ */ jsx2(AlertTriangle, { size: 11 }) : /* @__PURE__ */ jsx2(Ticks, { double: status !== "sent" }) });
79
+ }
80
+
81
+ // src/AudioPlayer.tsx
82
+ import { useEffect, useRef, useState } from "react";
83
+ import { Pause, Play } from "lucide-react";
84
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
85
+ var BARS = Array.from({ length: 30 }, (_, i) => {
86
+ const heights = [3, 5, 8, 12, 7, 10, 14, 9, 6, 11, 15, 8, 4, 13, 7, 10, 6, 12, 9, 5, 14, 8, 11, 6, 13, 7, 10, 5, 9, 4];
87
+ return heights[i % heights.length];
88
+ });
89
+ function fmt(sec) {
90
+ if (!isFinite(sec)) return "0:00";
91
+ const m = Math.floor(sec / 60);
92
+ const s = Math.floor(sec % 60);
93
+ return `${m}:${s.toString().padStart(2, "0")}`;
94
+ }
95
+ function AudioPlayer({ src, isMine = false }) {
96
+ const audioRef = useRef(null);
97
+ const [playing, setPlaying] = useState(false);
98
+ const [progress, setProgress] = useState(0);
99
+ const [duration, setDuration] = useState(0);
100
+ const [current, setCurrent] = useState(0);
101
+ useEffect(() => {
102
+ const audio = audioRef.current;
103
+ if (!audio) return;
104
+ const onTime = () => {
105
+ setCurrent(audio.currentTime);
106
+ setProgress(audio.duration ? audio.currentTime / audio.duration : 0);
107
+ };
108
+ const onMeta = () => setDuration(audio.duration);
109
+ const onEnd = () => {
110
+ setPlaying(false);
111
+ setProgress(0);
112
+ setCurrent(0);
113
+ };
114
+ audio.addEventListener("timeupdate", onTime);
115
+ audio.addEventListener("loadedmetadata", onMeta);
116
+ audio.addEventListener("ended", onEnd);
117
+ return () => {
118
+ audio.removeEventListener("timeupdate", onTime);
119
+ audio.removeEventListener("loadedmetadata", onMeta);
120
+ audio.removeEventListener("ended", onEnd);
121
+ };
122
+ }, []);
123
+ const toggle = () => {
124
+ const audio = audioRef.current;
125
+ if (!audio) return;
126
+ if (playing) {
127
+ audio.pause();
128
+ setPlaying(false);
129
+ } else {
130
+ audio.play();
131
+ setPlaying(true);
132
+ }
133
+ };
134
+ const seek = (e) => {
135
+ const audio = audioRef.current;
136
+ if (!audio || !audio.duration) return;
137
+ const rect = e.currentTarget.getBoundingClientRect();
138
+ const ratio = (e.clientX - rect.left) / rect.width;
139
+ audio.currentTime = ratio * audio.duration;
140
+ };
141
+ const activeBars = Math.round(progress * BARS.length);
142
+ const playBtn = isMine ? "bg-green-600 hover:bg-green-700 text-white" : "bg-gray-600 hover:bg-gray-700 text-white";
143
+ const activeBar = isMine ? "bg-green-700" : "bg-gray-700";
144
+ const inactiveBar = isMine ? "bg-green-300" : "bg-gray-300";
145
+ return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 w-full", style: { minWidth: "200px", maxWidth: "260px" }, children: [
146
+ /* @__PURE__ */ jsx3("audio", { ref: audioRef, src, preload: "metadata" }),
147
+ /* @__PURE__ */ jsx3(
148
+ "button",
149
+ {
150
+ onClick: toggle,
151
+ className: `flex-shrink-0 w-9 h-9 rounded-full flex items-center justify-center transition-colors ${playBtn}`,
152
+ children: playing ? /* @__PURE__ */ jsx3(Pause, { size: 16 }) : /* @__PURE__ */ jsx3(Play, { size: 16, className: "translate-x-0.5" })
153
+ }
154
+ ),
155
+ /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-1", children: [
156
+ /* @__PURE__ */ jsx3("div", { className: "flex items-end gap-0.5 h-8 cursor-pointer", onClick: seek, children: BARS.map((h, i) => /* @__PURE__ */ jsx3(
157
+ "div",
158
+ {
159
+ className: `flex-1 rounded-full transition-colors ${i < activeBars ? activeBar : inactiveBar}`,
160
+ style: { height: `${h * 2}px` }
161
+ },
162
+ i
163
+ )) }),
164
+ /* @__PURE__ */ jsx3("span", { className: "text-gray-400 tabular-nums text-xs", children: playing || current > 0 ? fmt(current) : fmt(duration) })
165
+ ] })
166
+ ] });
167
+ }
168
+
169
+ // src/FileIcon.tsx
170
+ import {
171
+ FileArchive,
172
+ FileAudio,
173
+ FileImage,
174
+ FileSpreadsheet,
175
+ FileText,
176
+ FileVideo,
177
+ File as FileGeneric,
178
+ Presentation
179
+ } from "lucide-react";
180
+
181
+ // src/lib/cn.ts
182
+ import { clsx } from "clsx";
183
+ import { twMerge } from "tailwind-merge";
184
+ function cn(...inputs) {
185
+ return twMerge(clsx(inputs));
186
+ }
187
+
188
+ // src/FileIcon.tsx
189
+ import { jsx as jsx4 } from "react/jsx-runtime";
190
+ var IMAGE_STYLE = { Icon: FileImage, colorClass: "text-violet-500" };
191
+ var VIDEO_STYLE = { Icon: FileVideo, colorClass: "text-fuchsia-500" };
192
+ var AUDIO_STYLE = { Icon: FileAudio, colorClass: "text-amber-500" };
193
+ var SHEET_STYLE = { Icon: FileSpreadsheet, colorClass: "text-green-600" };
194
+ var WORD_STYLE = { Icon: FileText, colorClass: "text-blue-500" };
195
+ var SLIDES_STYLE = { Icon: Presentation, colorClass: "text-orange-600" };
196
+ var TEXT_STYLE = { Icon: FileText, colorClass: "text-gray-500" };
197
+ var EXTENSION_STYLE = {
198
+ pdf: { Icon: FileText, colorClass: "text-red-500" },
199
+ doc: WORD_STYLE,
200
+ docx: WORD_STYLE,
201
+ xls: SHEET_STYLE,
202
+ xlsx: SHEET_STYLE,
203
+ csv: SHEET_STYLE,
204
+ ppt: SLIDES_STYLE,
205
+ pptx: SLIDES_STYLE,
206
+ zip: { Icon: FileArchive, colorClass: "text-orange-500" },
207
+ txt: TEXT_STYLE,
208
+ plain: TEXT_STYLE,
209
+ image: IMAGE_STYLE,
210
+ jpg: IMAGE_STYLE,
211
+ jpeg: IMAGE_STYLE,
212
+ png: IMAGE_STYLE,
213
+ webp: IMAGE_STYLE,
214
+ gif: IMAGE_STYLE,
215
+ heic: IMAGE_STYLE,
216
+ video: VIDEO_STYLE,
217
+ mp4: VIDEO_STYLE,
218
+ "3gp": VIDEO_STYLE,
219
+ "3gpp": VIDEO_STYLE,
220
+ mov: VIDEO_STYLE,
221
+ webm: VIDEO_STYLE,
222
+ audio: AUDIO_STYLE,
223
+ mp3: AUDIO_STYLE,
224
+ mpeg: AUDIO_STYLE,
225
+ ogg: AUDIO_STYLE,
226
+ oga: AUDIO_STYLE,
227
+ opus: AUDIO_STYLE,
228
+ aac: AUDIO_STYLE,
229
+ amr: AUDIO_STYLE,
230
+ m4a: AUDIO_STYLE,
231
+ wav: AUDIO_STYLE
232
+ };
233
+ var MEDIA_FAMILIES = /* @__PURE__ */ new Set(["image", "video", "audio"]);
234
+ function resolveFileIconExtension(filename, mimeType) {
235
+ const fromFilename = filename?.split(".").pop()?.toLowerCase();
236
+ if (fromFilename && EXTENSION_STYLE[fromFilename]) return fromFilename;
237
+ const [family, subtype] = (mimeType ?? "").split(";")[0].toLowerCase().split("/");
238
+ if (family && MEDIA_FAMILIES.has(family)) return family;
239
+ return subtype ?? "";
240
+ }
241
+ function FileIcon({ filename, mimeType, size = 20, className }) {
242
+ const extension = resolveFileIconExtension(filename, mimeType);
243
+ const style = EXTENSION_STYLE[extension] ?? { Icon: FileGeneric, colorClass: "text-gray-500" };
244
+ const { Icon, colorClass } = style;
245
+ return /* @__PURE__ */ jsx4(Icon, { size, className: cn(colorClass, className) });
246
+ }
247
+
248
+ // src/lib/format.ts
249
+ function formatTimestamp(timestamp) {
250
+ try {
251
+ const date = new Date(timestamp);
252
+ const hours = date.getHours().toString().padStart(2, "0");
253
+ const minutes = date.getMinutes().toString().padStart(2, "0");
254
+ return `${hours}:${minutes}`;
255
+ } catch {
256
+ return timestamp;
257
+ }
258
+ }
259
+ function formatDateTime(iso) {
260
+ const d = new Date(iso);
261
+ return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
262
+ }
263
+ function isSameDay(a, b) {
264
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
265
+ }
266
+ var FILE_SIZE_UNITS = ["B", "KB", "MB", "GB"];
267
+ function formatFileSize(bytes) {
268
+ if (!isFinite(bytes) || bytes < 0) return "";
269
+ if (bytes < 1) return "0 B";
270
+ const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), FILE_SIZE_UNITS.length - 1);
271
+ const value = bytes / 1024 ** exponent;
272
+ const formatted = exponent === 0 ? value.toString() : value.toFixed(value < 10 ? 1 : 0);
273
+ return `${formatted} ${FILE_SIZE_UNITS[exponent]}`;
274
+ }
275
+
276
+ // src/MediaRenderer.tsx
277
+ import { useState as useState2 } from "react";
278
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
279
+ function documentTypeLabel(filename, mimeType) {
280
+ const extension = filename?.split(".").pop();
281
+ if (extension && extension.length <= 5 && extension !== filename) return extension.toUpperCase();
282
+ const subtype = mimeType?.split(";")[0]?.split("/")[1];
283
+ if (!subtype) return "FILE";
284
+ const compacto = subtype.split(".").pop() ?? subtype;
285
+ return compacto.slice(0, 12).toUpperCase();
286
+ }
287
+ function resolveMediaSource(message) {
288
+ if (message.mediaUrl) return message.mediaUrl;
289
+ if (message.base64) {
290
+ const prefix = message.mimeType ? `data:${message.mimeType};base64,` : "data:application/octet-stream;base64,";
291
+ return prefix + message.base64;
292
+ }
293
+ return null;
294
+ }
295
+ function hasLazyRef(message) {
296
+ return Boolean(message.uploadId || message.mediaId);
297
+ }
298
+ function useLazyMediaUrl(message, onResolveUrl) {
299
+ const [url, setUrl] = useState2(null);
300
+ const [loading, setLoading] = useState2(false);
301
+ const [error, setError] = useState2(false);
302
+ const load = async () => {
303
+ if (url || loading || !onResolveUrl) return;
304
+ setLoading(true);
305
+ setError(false);
306
+ try {
307
+ const resolved = await onResolveUrl(message);
308
+ if (resolved) setUrl(resolved);
309
+ else setError(true);
310
+ } catch {
311
+ setError(true);
312
+ } finally {
313
+ setLoading(false);
314
+ }
315
+ };
316
+ return { url, loading, error, load };
317
+ }
318
+ function MediaRenderer({ message, onLightbox, onResolveUrl, className }) {
319
+ const { bubble } = useConversationLocales();
320
+ const eagerSrc = resolveMediaSource(message);
321
+ const lazy = useLazyMediaUrl(message, onResolveUrl);
322
+ const src = eagerSrc ?? lazy.url;
323
+ const canLazyLoad = !eagerSrc && hasLazyRef(message) && Boolean(onResolveUrl);
324
+ function LazyMediaButton({ icon, label }) {
325
+ return /* @__PURE__ */ jsxs3(
326
+ "button",
327
+ {
328
+ onClick: lazy.load,
329
+ disabled: lazy.loading,
330
+ className: "flex min-w-[180px] items-center gap-2 rounded-lg bg-black/5 px-2 py-1.5 text-left transition-colors hover:bg-black/10 disabled:opacity-60 dark:bg-white/10 dark:hover:bg-white/15",
331
+ children: [
332
+ /* @__PURE__ */ jsx5("span", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-gray-600 text-white", children: icon }),
333
+ /* @__PURE__ */ jsx5("span", { className: "truncate text-xs text-gray-600 dark:text-gray-300", children: label })
334
+ ]
335
+ }
336
+ );
337
+ }
338
+ switch (message.type) {
339
+ case "image":
340
+ case "sticker": {
341
+ if (!src && canLazyLoad) {
342
+ return /* @__PURE__ */ jsx5(
343
+ LazyMediaButton,
344
+ {
345
+ icon: /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
346
+ /* @__PURE__ */ jsx5("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
347
+ /* @__PURE__ */ jsx5("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
348
+ /* @__PURE__ */ jsx5("polyline", { points: "21 15 16 10 5 21" })
349
+ ] }),
350
+ label: lazy.loading ? bubble.mediaLoading : lazy.error ? bubble.mediaRetry : bubble.viewImage
351
+ }
352
+ );
353
+ }
354
+ return /* @__PURE__ */ jsx5("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx5("img", { src, alt: message.caption ?? bubble.imageAlt, className: "w-full max-h-80 object-cover cursor-pointer hover:opacity-90 transition-opacity", onClick: () => onLightbox(src), loading: "lazy" }) : /* @__PURE__ */ jsx5("div", { className: "w-full h-40 bg-gray-200 flex items-center justify-center text-gray-400", children: /* @__PURE__ */ jsxs3("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
355
+ /* @__PURE__ */ jsx5("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
356
+ /* @__PURE__ */ jsx5("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
357
+ /* @__PURE__ */ jsx5("polyline", { points: "21 15 16 10 5 21" })
358
+ ] }) }) });
359
+ }
360
+ case "video": {
361
+ if (!src && canLazyLoad) {
362
+ return /* @__PURE__ */ jsx5(
363
+ LazyMediaButton,
364
+ {
365
+ icon: /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
366
+ /* @__PURE__ */ jsx5("polygon", { points: "23 7 16 12 23 17 23 7" }),
367
+ /* @__PURE__ */ jsx5("rect", { x: "1", y: "5", width: "15", height: "14", rx: "2", ry: "2" })
368
+ ] }),
369
+ label: lazy.loading ? bubble.mediaLoading : lazy.error ? bubble.mediaRetry : bubble.viewVideo
370
+ }
371
+ );
372
+ }
373
+ return /* @__PURE__ */ jsx5("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx5("video", { src, className: "w-full max-h-80 rounded-lg", controls: true, preload: "metadata", children: /* @__PURE__ */ jsx5("track", { kind: "captions" }) }) : /* @__PURE__ */ jsx5("div", { className: "w-full h-32 bg-gray-200 rounded-lg flex items-center justify-center text-gray-400", children: /* @__PURE__ */ jsxs3("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
374
+ /* @__PURE__ */ jsx5("polygon", { points: "23 7 16 12 23 17 23 7" }),
375
+ /* @__PURE__ */ jsx5("rect", { x: "1", y: "5", width: "15", height: "14", rx: "2", ry: "2" })
376
+ ] }) }) });
377
+ }
378
+ case "audio": {
379
+ if (!src && canLazyLoad) {
380
+ return /* @__PURE__ */ jsx5(
381
+ LazyMediaButton,
382
+ {
383
+ icon: /* @__PURE__ */ jsx5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", className: "translate-x-0.5", children: /* @__PURE__ */ jsx5("polygon", { points: "5 3 19 12 5 21 5 3" }) }),
384
+ label: lazy.loading ? bubble.mediaLoading : lazy.error ? bubble.mediaRetry : bubble.listenAudio
385
+ }
386
+ );
387
+ }
388
+ return /* @__PURE__ */ jsx5("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx5(AudioPlayer, { src, isMine: message.direction === "outbound" }) : /* @__PURE__ */ jsx5("div", { className: "h-12 bg-gray-200 rounded-lg flex items-center justify-center text-gray-400 text-xs", children: bubble.mediaUnavailable }) });
389
+ }
390
+ case "document": {
391
+ const typeLabel = documentTypeLabel(message.filename, message.mimeType);
392
+ const sizeLabel = message.sizeBytes ? formatFileSize(message.sizeBytes) : null;
393
+ return /* @__PURE__ */ jsxs3("div", { className: cn("flex items-center gap-3 min-w-[200px]", className), children: [
394
+ /* @__PURE__ */ jsx5("div", { className: "w-10 h-10 bg-gray-200 rounded-lg flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx5(FileIcon, { filename: message.filename, mimeType: message.mimeType }) }),
395
+ /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
396
+ /* @__PURE__ */ jsx5("p", { className: "text-sm font-medium truncate", children: message.filename ?? bubble.untitledDocument }),
397
+ /* @__PURE__ */ jsx5("p", { className: "truncate text-xs text-gray-500", children: sizeLabel ? `${typeLabel} \xB7 ${sizeLabel}` : typeLabel })
398
+ ] }),
399
+ src ? /* @__PURE__ */ jsx5("a", { href: src, download: message.filename, className: "w-8 h-8 flex items-center justify-center rounded-full bg-gray-200 hover:bg-gray-300 flex-shrink-0 transition-colors", "aria-label": bubble.downloadFile, children: /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-gray-600", children: [
400
+ /* @__PURE__ */ jsx5("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
401
+ /* @__PURE__ */ jsx5("polyline", { points: "7 10 12 15 17 10" }),
402
+ /* @__PURE__ */ jsx5("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
403
+ ] }) }) : canLazyLoad ? /* @__PURE__ */ jsx5(
404
+ "button",
405
+ {
406
+ onClick: lazy.load,
407
+ disabled: lazy.loading,
408
+ className: "w-8 h-8 flex items-center justify-center rounded-full bg-gray-200 hover:bg-gray-300 flex-shrink-0 transition-colors disabled:opacity-50",
409
+ "aria-label": bubble.downloadFile,
410
+ children: /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-gray-600", children: [
411
+ /* @__PURE__ */ jsx5("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
412
+ /* @__PURE__ */ jsx5("polyline", { points: "7 10 12 15 17 10" }),
413
+ /* @__PURE__ */ jsx5("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
414
+ ] })
415
+ }
416
+ ) : null,
417
+ lazy.error && /* @__PURE__ */ jsx5("span", { className: "text-xs text-red-500 flex-shrink-0", children: bubble.mediaError })
418
+ ] });
419
+ }
420
+ default:
421
+ return null;
422
+ }
423
+ }
424
+
425
+ // src/Lightbox.tsx
426
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
427
+ var DEFAULT_LIGHTBOX_LABELS = {
428
+ imageAlt: "Imagem",
429
+ close: "Fechar"
430
+ };
431
+ function Lightbox({ imageUrl, caption, onClose, labels }) {
432
+ const imageAltLabel = labels?.imageAlt ?? DEFAULT_LIGHTBOX_LABELS.imageAlt;
433
+ const closeLabel = labels?.close ?? DEFAULT_LIGHTBOX_LABELS.close;
434
+ return /* @__PURE__ */ jsx6("div", { className: "fixed inset-0 z-50 bg-black/85 flex items-center justify-center p-4", onClick: onClose, children: /* @__PURE__ */ jsxs4("div", { className: "max-w-[90vw] max-h-[90vh] flex flex-col items-center", onClick: (e) => e.stopPropagation(), children: [
435
+ /* @__PURE__ */ jsx6("img", { src: imageUrl, alt: caption ?? imageAltLabel, className: "max-w-full max-h-[80vh] object-contain rounded-lg" }),
436
+ caption && /* @__PURE__ */ jsx6("p", { className: "text-white text-sm mt-3 text-center", children: caption }),
437
+ /* @__PURE__ */ jsx6("button", { onClick: onClose, className: "mt-4 px-4 py-2 bg-white/20 text-white rounded-lg hover:bg-white/30 transition-colors", children: closeLabel })
438
+ ] }) });
439
+ }
440
+
441
+ // src/InteractiveMessage.tsx
442
+ import { useState as useState3 } from "react";
443
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
444
+ var FORMATTING_CLASSES = "[&_strong]:font-bold [&_em]:italic [&_del]:line-through";
445
+ var DEFAULT_INTERACTIVE_MESSAGE_LABELS = {
446
+ openList: "Ver op\xE7\xF5es"
447
+ };
448
+ function collectRows(payload) {
449
+ return (payload.action?.sections ?? []).flatMap((section) => section.rows ?? []);
450
+ }
451
+ function collectButtons(payload) {
452
+ return (payload.action?.buttons ?? []).map((button) => button.reply).filter((reply) => reply !== void 0);
453
+ }
454
+ function InteractiveMessage({ payload, onSelect, labels, className }) {
455
+ const openListLabel = labels?.openList ?? DEFAULT_INTERACTIVE_MESSAGE_LABELS.openList;
456
+ const [isListOpen, setIsListOpen] = useState3(false);
457
+ const buttons = collectButtons(payload);
458
+ const sections = payload.action?.sections ?? [];
459
+ const hasRows = collectRows(payload).length > 0;
460
+ const isInteractable = onSelect !== void 0;
461
+ return /* @__PURE__ */ jsxs5("div", { className: cn("flex flex-col gap-1", className), children: [
462
+ payload.header?.text ? /* @__PURE__ */ jsx7("p", { className: cn("text-sm font-semibold text-gray-900 dark:text-gray-100", FORMATTING_CLASSES), children: parseWhatsAppFormatting(payload.header.text) }) : null,
463
+ payload.body?.text ? /* @__PURE__ */ jsx7(
464
+ "p",
465
+ {
466
+ className: cn(
467
+ "whitespace-pre-wrap break-words text-sm text-gray-900 dark:text-gray-100",
468
+ FORMATTING_CLASSES
469
+ ),
470
+ children: parseWhatsAppFormatting(payload.body.text)
471
+ }
472
+ ) : null,
473
+ payload.footer?.text ? /* @__PURE__ */ jsx7("p", { className: cn("text-xs text-gray-500 dark:text-gray-400", FORMATTING_CLASSES), children: parseWhatsAppFormatting(payload.footer.text) }) : null,
474
+ buttons.length > 0 ? /* @__PURE__ */ jsx7("div", { className: "mt-1 flex flex-col gap-1 border-t border-gray-200 pt-1 dark:border-gray-700", children: buttons.map((button) => /* @__PURE__ */ jsx7(
475
+ "button",
476
+ {
477
+ type: "button",
478
+ disabled: !isInteractable,
479
+ onClick: () => onSelect?.({ kind: "button", option: button }),
480
+ className: "rounded-md px-3 py-1.5 text-sm font-medium text-teal-700 transition-colors enabled:hover:bg-teal-50 disabled:cursor-default dark:text-teal-300 dark:enabled:hover:bg-teal-900/30",
481
+ children: button.title
482
+ },
483
+ button.id
484
+ )) }) : null,
485
+ hasRows ? /* @__PURE__ */ jsxs5("div", { className: "mt-1 border-t border-gray-200 pt-1 dark:border-gray-700", children: [
486
+ /* @__PURE__ */ jsxs5(
487
+ "button",
488
+ {
489
+ type: "button",
490
+ onClick: () => setIsListOpen((open) => !open),
491
+ "aria-expanded": isListOpen,
492
+ className: "w-full rounded-md px-3 py-1.5 text-sm font-medium text-teal-700 transition-colors hover:bg-teal-50 dark:text-teal-300 dark:hover:bg-teal-900/30",
493
+ children: [
494
+ "\u2630 ",
495
+ payload.action?.button ?? openListLabel
496
+ ]
497
+ }
498
+ ),
499
+ isListOpen ? /* @__PURE__ */ jsx7("div", { className: "mt-1 flex flex-col gap-1", children: sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs5("div", { className: "flex flex-col", children: [
500
+ section.title ? /* @__PURE__ */ jsx7("p", { className: "px-3 py-1 text-xs font-semibold uppercase text-gray-500 dark:text-gray-400", children: section.title }) : null,
501
+ (section.rows ?? []).map((row) => /* @__PURE__ */ jsxs5(
502
+ "button",
503
+ {
504
+ type: "button",
505
+ disabled: !isInteractable,
506
+ onClick: () => onSelect?.({ kind: "list", option: row }),
507
+ className: "rounded-md px-3 py-1.5 text-left text-sm text-gray-900 transition-colors enabled:hover:bg-gray-100 disabled:cursor-default dark:text-gray-100 dark:enabled:hover:bg-gray-700",
508
+ children: [
509
+ /* @__PURE__ */ jsx7("span", { className: "block", children: row.title }),
510
+ row.description ? /* @__PURE__ */ jsx7("span", { className: "block text-xs text-gray-500 dark:text-gray-400", children: row.description }) : null
511
+ ]
512
+ },
513
+ row.id
514
+ ))
515
+ ] }, section.title ?? sectionIndex)) }) : null
516
+ ] }) : null
517
+ ] });
518
+ }
519
+
520
+ // src/lib/createMediaUrlResolver.ts
521
+ function createMediaUrlResolver(api) {
522
+ return async (message) => {
523
+ if (message.uploadId) return api.getDocumentUrl(message.uploadId, "inline");
524
+ if (message.mediaId) {
525
+ const { mimeType, data } = await api.getMediaProxyUrl(message.mediaId);
526
+ return `data:${mimeType};base64,${data}`;
527
+ }
528
+ return null;
529
+ };
530
+ }
531
+
532
+ // src/providers/ConversationsProvider.tsx
533
+ import { createContext as createContext2, useContext as useContext2 } from "react";
534
+ import { jsx as jsx8 } from "react/jsx-runtime";
535
+ var ConversationsContext = createContext2(null);
536
+ function ConversationsProvider({
537
+ api,
538
+ sse,
539
+ children
540
+ }) {
541
+ return /* @__PURE__ */ jsx8(ConversationsContext.Provider, { value: { api, sse }, children });
542
+ }
543
+ function useConversations() {
544
+ return useContext2(ConversationsContext);
545
+ }
546
+
547
+ // src/MessageBubble.tsx
548
+ import { useMemo, useState as useState4 } from "react";
549
+ import { Check } from "lucide-react";
550
+ import { Fragment, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
551
+ var BUBBLE_COLOR = {
552
+ agent: "bg-[#d9fdd3] dark:bg-[#005c4b]",
553
+ bot: "bg-[#d7f0ec] dark:bg-[#0a3d3a]",
554
+ customer: "bg-white dark:bg-[#202c33]"
555
+ };
556
+ var MEDIA_TYPES = /* @__PURE__ */ new Set(["image", "audio", "video", "document", "sticker"]);
557
+ function MessageBubble({
558
+ message,
559
+ isMine,
560
+ senderName,
561
+ isFirstInGroup = true,
562
+ isSelecting = false,
563
+ isSelected = false,
564
+ onToggleSelect,
565
+ onResolveMediaUrl,
566
+ onInteractiveSelect,
567
+ className
568
+ }) {
569
+ const { bubble, selection } = useConversationLocales();
570
+ const [lightboxSrc, setLightboxSrc] = useState4(null);
571
+ const context = useConversations();
572
+ const resolveMediaUrl = useMemo(
573
+ () => onResolveMediaUrl ?? (context?.api ? createMediaUrlResolver(context.api) : void 0),
574
+ [onResolveMediaUrl, context?.api]
575
+ );
576
+ const bubbleColor = BUBBLE_COLOR[message.sender] ?? BUBBLE_COLOR.customer;
577
+ const hasError = message.status === "failed";
578
+ const isMedia = MEDIA_TYPES.has(message.type);
579
+ const isTemplate = message.type === "template";
580
+ const isInteractive = message.type === "interactive";
581
+ const displayName = message.sender === "agent" && senderName ? senderName : bubble[message.sender] ?? message.sender;
582
+ const tooltipText = message.status === "read" && message.readAt ? `${bubble.readAt}${formatDateTime(message.readAt)}` : message.status === "failed" ? bubble.windowExpired : void 0;
583
+ const tailCornerClass = isFirstInGroup ? isMine ? "rounded-tr-md" : "rounded-tl-md" : "";
584
+ const checkbox = /* @__PURE__ */ jsx9(
585
+ "button",
586
+ {
587
+ onClick: (e) => {
588
+ e.stopPropagation();
589
+ onToggleSelect?.();
590
+ },
591
+ title: selection.select,
592
+ className: `
593
+ flex-shrink-0 self-end mb-1 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all
594
+ ${isSelected ? "bg-teal-600 border-teal-600" : "bg-white/80 dark:bg-black/40 border-black/20 dark:border-white/30"}
595
+ ${isSelecting ? "opacity-100" : "opacity-0 group-hover:opacity-100"}
596
+ `,
597
+ children: isSelected && /* @__PURE__ */ jsx9(Check, { size: 12, className: "text-white", strokeWidth: 3 })
598
+ }
599
+ );
600
+ return /* @__PURE__ */ jsxs6(
601
+ "div",
602
+ {
603
+ className: cn(
604
+ "flex items-end gap-1.5 group",
605
+ isMine ? "justify-end" : "justify-start",
606
+ isFirstInGroup ? "mt-2" : "mt-0.5",
607
+ className
608
+ ),
609
+ children: [
610
+ isMine && checkbox,
611
+ /* @__PURE__ */ jsxs6(
612
+ "div",
613
+ {
614
+ onClick: isSelecting ? onToggleSelect : void 0,
615
+ className: `
616
+ max-w-[75%] sm:max-w-[65%] rounded-2xl ${tailCornerClass} px-2.5 py-1.5 shadow-sm
617
+ ${bubbleColor}
618
+ ${hasError ? "ring-1 ring-inset ring-red-400" : ""}
619
+ ${isSelecting ? "cursor-pointer" : ""}
620
+ ${isSelected ? "ring-2 ring-teal-500" : ""}
621
+ relative
622
+ `,
623
+ children: [
624
+ isMine && isFirstInGroup && (message.sender === "bot" || message.sender === "agent" && senderName) && /* @__PURE__ */ jsx9("div", { className: "text-xs font-semibold mb-0.5 text-teal-700 dark:text-teal-400", children: displayName }),
625
+ message.moderation?.isOffensive && /* @__PURE__ */ jsxs6(
626
+ "div",
627
+ {
628
+ className: "mb-1 inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-800 dark:bg-amber-950/60 dark:text-amber-300",
629
+ title: message.moderation.terms.length > 0 ? message.moderation.terms.join(", ") : void 0,
630
+ children: [
631
+ /* @__PURE__ */ jsx9("span", { "aria-hidden": true, children: "\u26A0\uFE0F" }),
632
+ /* @__PURE__ */ jsx9("span", { children: bubble.moderationFlagged })
633
+ ]
634
+ }
635
+ ),
636
+ isMedia ? /* @__PURE__ */ jsx9(MediaRenderer, { message, onLightbox: setLightboxSrc, onResolveUrl: resolveMediaUrl }) : isInteractive && message.payload ? (
637
+ // O texto da mensagem interativa mora dentro do payload (`body.text`), e `content` guarda
638
+ // só uma cópia achatada para busca — renderizar `content` aqui duplicaria o corpo.
639
+ /* @__PURE__ */ jsx9(InteractiveMessage, { payload: message.payload, onSelect: onInteractiveSelect })
640
+ ) : /* @__PURE__ */ jsxs6(Fragment, { children: [
641
+ isTemplate && /* @__PURE__ */ jsxs6("p", { className: "text-xs text-gray-500 dark:text-gray-400 flex items-center gap-1 mb-0.5", children: [
642
+ /* @__PURE__ */ jsx9("span", { children: "\u{1F4E8}" }),
643
+ /* @__PURE__ */ jsxs6("span", { className: "font-medium", children: [
644
+ bubble.templateLabel,
645
+ message.templateName ? ` \u2014 ${message.templateName}` : ""
646
+ ] })
647
+ ] }),
648
+ /* @__PURE__ */ jsx9("div", { className: "text-sm text-gray-900 dark:text-gray-100 whitespace-pre-wrap break-words leading-[19px]", children: parseWhatsAppFormatting(message.content ?? "") })
649
+ ] }),
650
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-end gap-1 mt-0.5 select-none", children: [
651
+ /* @__PURE__ */ jsx9("span", { className: "text-xs text-black/40 dark:text-white/40 font-medium", children: formatTimestamp(message.timestamp) }),
652
+ isMine && message.status && /* @__PURE__ */ jsx9(StatusTicks, { status: message.status, title: tooltipText })
653
+ ] })
654
+ ]
655
+ }
656
+ ),
657
+ !isMine && checkbox,
658
+ lightboxSrc && /* @__PURE__ */ jsx9(Lightbox, { imageUrl: lightboxSrc, onClose: () => setLightboxSrc(null) })
659
+ ]
660
+ }
661
+ );
662
+ }
663
+
664
+ // src/Wallpaper.tsx
665
+ import { jsx as jsx10 } from "react/jsx-runtime";
666
+ function doodlePattern(strokeColor, opacity) {
667
+ const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><g fill='none' stroke='${strokeColor}' stroke-width='1.2' opacity='${opacity}'><circle cx='15' cy='15' r='3'/><path d='M40 10 q5 8 0 16 q-5 -8 0 -16z'/><circle cx='70' cy='28' r='2'/><path d='M18 55 l6 6 m-6 0 l6 -6'/><circle cx='55' cy='68' r='2.5'/><path d='M85 58 q6 6 0 12 q-6 -6 0 -12z'/><circle cx='8' cy='85' r='2'/><path d='M65 90 l5 5 m-5 0 l5 -5'/></g></svg>`;
668
+ return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
669
+ }
670
+ var LIGHT_WALLPAPER = {
671
+ backgroundColor: "#efeae2",
672
+ backgroundImage: doodlePattern("#d7cfc0", 0.7),
673
+ backgroundRepeat: "repeat",
674
+ backgroundSize: "100px 100px"
675
+ };
676
+ var DARK_WALLPAPER = {
677
+ backgroundColor: "#0b141a",
678
+ backgroundImage: doodlePattern("#19232a", 0.9),
679
+ backgroundRepeat: "repeat",
680
+ backgroundSize: "100px 100px"
681
+ };
682
+ function ConversationWallpaper({ children, className, style }) {
683
+ const isDark = useIsDarkTheme();
684
+ return /* @__PURE__ */ jsx10(
685
+ "div",
686
+ {
687
+ className: cn("cv-wallpaper", className),
688
+ style: { ...isDark ? DARK_WALLPAPER : LIGHT_WALLPAPER, ...style },
689
+ children
690
+ }
691
+ );
692
+ }
693
+
694
+ // src/emojiCatalog.ts
695
+ var EMOJI_CATEGORIES = [
696
+ {
697
+ name: "Smileys",
698
+ entries: [
699
+ { emoji: "\u{1F600}", keywords: ["sorriso", "feliz", "alegre"] },
700
+ { emoji: "\u{1F603}", keywords: ["sorriso", "feliz", "animado"] },
701
+ { emoji: "\u{1F604}", keywords: ["sorriso", "feliz", "risada"] },
702
+ { emoji: "\u{1F601}", keywords: ["sorriso", "dentes", "feliz"] },
703
+ { emoji: "\u{1F605}", keywords: ["alivio", "al\xEDvio", "suor", "nervoso"] },
704
+ { emoji: "\u{1F602}", keywords: ["risada", "chorando", "engracado", "engra\xE7ado"] },
705
+ { emoji: "\u{1F923}", keywords: ["risada", "rolando", "engracado", "engra\xE7ado"] },
706
+ { emoji: "\u{1F60A}", keywords: ["sorriso", "timido", "t\xEDmido", "feliz"] },
707
+ { emoji: "\u{1F607}", keywords: ["anjo", "inocente", "santo"] },
708
+ { emoji: "\u{1F642}", keywords: ["sorriso", "leve", "ok"] },
709
+ { emoji: "\u{1F609}", keywords: ["piscada", "piscar", "flerte"] },
710
+ { emoji: "\u{1F60C}", keywords: ["aliviado", "calmo", "tranquilo"] },
711
+ { emoji: "\u{1F60D}", keywords: ["amor", "apaixonado", "coracao", "cora\xE7\xE3o", "olhos"] },
712
+ { emoji: "\u{1F970}", keywords: ["amor", "apaixonado", "carinho"] },
713
+ { emoji: "\u{1F618}", keywords: ["beijo", "amor", "carinho"] },
714
+ { emoji: "\u{1F60B}", keywords: ["gostoso", "delicia", "del\xEDcia", "lingua", "l\xEDngua"] },
715
+ { emoji: "\u{1F61C}", keywords: ["lingua", "l\xEDngua", "brincadeira", "piscada"] },
716
+ { emoji: "\u{1F92A}", keywords: ["maluco", "doido", "brincadeira"] },
717
+ { emoji: "\u{1F914}", keywords: ["pensando", "duvida", "d\xFAvida", "hmm"] },
718
+ { emoji: "\u{1F917}", keywords: ["abraco", "abra\xE7o", "carinho"] },
719
+ { emoji: "\u{1F610}", keywords: ["neutro", "serio", "s\xE9rio", "indiferente"] },
720
+ { emoji: "\u{1F634}", keywords: ["dormindo", "sono", "cansado"] },
721
+ { emoji: "\u{1F62D}", keywords: ["chorando", "triste", "lagrima", "l\xE1grima"] },
722
+ { emoji: "\u{1F622}", keywords: ["triste", "chorando", "lagrima", "l\xE1grima"] },
723
+ { emoji: "\u{1F621}", keywords: ["raiva", "bravo", "irritado"] },
724
+ { emoji: "\u{1F631}", keywords: ["susto", "medo", "assustado"] },
725
+ { emoji: "\u{1F92F}", keywords: ["explodindo", "chocado", "surpresa"] },
726
+ { emoji: "\u{1F60E}", keywords: ["oculos", "\xF3culos", "legal", "estiloso"] },
727
+ { emoji: "\u{1F973}", keywords: ["festa", "comemorar", "aniversario", "anivers\xE1rio"] },
728
+ { emoji: "\u{1F637}", keywords: ["mascara", "m\xE1scara", "doente", "saude", "sa\xFAde"] }
729
+ ]
730
+ },
731
+ {
732
+ name: "Gestos",
733
+ entries: [
734
+ { emoji: "\u{1F44D}", keywords: ["joia", "j\xF3ia", "polegar", "ok", "positivo", "curtir"] },
735
+ { emoji: "\u{1F44E}", keywords: ["polegar", "negativo", "ruim", "nao", "n\xE3o"] },
736
+ { emoji: "\u{1F44C}", keywords: ["ok", "certo", "perfeito"] },
737
+ { emoji: "\u270C\uFE0F", keywords: ["paz", "vitoria", "vit\xF3ria", "dois"] },
738
+ { emoji: "\u{1F91E}", keywords: ["sorte", "dedos", "cruzados", "torcendo"] },
739
+ { emoji: "\u{1F919}", keywords: ["chama", "ligar", "shaka"] },
740
+ { emoji: "\u{1F44B}", keywords: ["tchau", "ola", "ol\xE1", "aceno", "oi"] },
741
+ { emoji: "\u270B", keywords: ["mao", "m\xE3o", "parar", "pare"] },
742
+ { emoji: "\u{1F44F}", keywords: ["palmas", "aplauso", "parabens", "parab\xE9ns"] },
743
+ { emoji: "\u{1F64C}", keywords: ["comemorar", "maos", "m\xE3os", "sucesso"] },
744
+ { emoji: "\u{1F91D}", keywords: ["acordo", "aperto", "mao", "m\xE3o", "negocio", "neg\xF3cio", "parceria"] },
745
+ { emoji: "\u{1F64F}", keywords: ["obrigado", "reza", "oracao", "ora\xE7\xE3o", "por favor"] },
746
+ { emoji: "\u270D\uFE0F", keywords: ["escrever", "assinar", "assinatura"] },
747
+ { emoji: "\u{1F4AA}", keywords: ["forca", "for\xE7a", "musculo", "m\xFAsculo", "braco", "bra\xE7o"] },
748
+ { emoji: "\u{1F447}", keywords: ["abaixo", "baixo", "apontar", "seta"] },
749
+ { emoji: "\u{1F449}", keywords: ["direita", "apontar", "seta"] },
750
+ { emoji: "\u261D\uFE0F", keywords: ["acima", "cima", "apontar", "atencao", "aten\xE7\xE3o"] }
751
+ ]
752
+ },
753
+ {
754
+ name: "Cora\xE7\xF5es",
755
+ entries: [
756
+ { emoji: "\u2764\uFE0F", keywords: ["coracao", "cora\xE7\xE3o", "amor", "vermelho"] },
757
+ { emoji: "\u{1F9E1}", keywords: ["coracao", "cora\xE7\xE3o", "laranja"] },
758
+ { emoji: "\u{1F49B}", keywords: ["coracao", "cora\xE7\xE3o", "amarelo"] },
759
+ { emoji: "\u{1F49A}", keywords: ["coracao", "cora\xE7\xE3o", "verde"] },
760
+ { emoji: "\u{1F499}", keywords: ["coracao", "cora\xE7\xE3o", "azul"] },
761
+ { emoji: "\u{1F49C}", keywords: ["coracao", "cora\xE7\xE3o", "roxo"] },
762
+ { emoji: "\u{1F5A4}", keywords: ["coracao", "cora\xE7\xE3o", "preto"] },
763
+ { emoji: "\u{1F90D}", keywords: ["coracao", "cora\xE7\xE3o", "branco"] },
764
+ { emoji: "\u{1F494}", keywords: ["coracao", "cora\xE7\xE3o", "partido", "triste"] },
765
+ { emoji: "\u{1F495}", keywords: ["coracao", "cora\xE7\xE3o", "amor", "casal"] },
766
+ { emoji: "\u{1F496}", keywords: ["coracao", "cora\xE7\xE3o", "brilho", "amor"] },
767
+ { emoji: "\u{1F49D}", keywords: ["coracao", "cora\xE7\xE3o", "presente", "laco", "la\xE7o"] }
768
+ ]
769
+ },
770
+ {
771
+ name: "Neg\xF3cios",
772
+ entries: [
773
+ { emoji: "\u{1F3E0}", keywords: ["casa", "imovel", "im\xF3vel", "residencia", "resid\xEAncia", "moradia"] },
774
+ { emoji: "\u{1F3E1}", keywords: ["casa", "imovel", "im\xF3vel", "jardim", "moradia"] },
775
+ { emoji: "\u{1F3E2}", keywords: ["predio", "pr\xE9dio", "empresa", "escritorio", "escrit\xF3rio"] },
776
+ { emoji: "\u{1F3E6}", keywords: ["banco", "financiamento", "agencia", "ag\xEAncia"] },
777
+ { emoji: "\u{1F511}", keywords: ["chave", "casa", "entrega", "imovel", "im\xF3vel"] },
778
+ { emoji: "\u{1F4C4}", keywords: ["documento", "papel", "contrato", "arquivo"] },
779
+ { emoji: "\u{1F4CB}", keywords: ["prancheta", "lista", "documento", "checklist"] },
780
+ { emoji: "\u{1F4DD}", keywords: ["anotar", "escrever", "nota", "formulario", "formul\xE1rio"] },
781
+ { emoji: "\u2705", keywords: ["ok", "certo", "aprovado", "concluido", "conclu\xEDdo", "check"] },
782
+ { emoji: "\u274C", keywords: ["errado", "negado", "recusado", "cancelar"] },
783
+ { emoji: "\u26A0\uFE0F", keywords: ["atencao", "aten\xE7\xE3o", "alerta", "cuidado"] },
784
+ { emoji: "\u{1F4B0}", keywords: ["dinheiro", "valor", "saco", "grana", "pagamento"] },
785
+ { emoji: "\u{1F4B5}", keywords: ["dinheiro", "nota", "valor", "pagamento"] },
786
+ { emoji: "\u{1F4B3}", keywords: ["cartao", "cart\xE3o", "credito", "cr\xE9dito", "pagamento"] },
787
+ { emoji: "\u{1F9FE}", keywords: ["recibo", "nota", "fiscal", "comprovante"] },
788
+ { emoji: "\u{1F4CA}", keywords: ["grafico", "gr\xE1fico", "relatorio", "relat\xF3rio", "dados"] },
789
+ { emoji: "\u{1F4C8}", keywords: ["grafico", "gr\xE1fico", "subindo", "crescimento", "alta"] },
790
+ { emoji: "\u{1F4C9}", keywords: ["grafico", "gr\xE1fico", "caindo", "queda", "baixa"] },
791
+ { emoji: "\u{1F5D3}\uFE0F", keywords: ["calendario", "calend\xE1rio", "data", "agenda", "prazo"] },
792
+ { emoji: "\u23F0", keywords: ["relogio", "rel\xF3gio", "hora", "prazo", "alarme"] },
793
+ { emoji: "\u{1F4DE}", keywords: ["telefone", "ligar", "contato", "chamada"] },
794
+ { emoji: "\u{1F4F1}", keywords: ["celular", "telefone", "whatsapp", "contato"] },
795
+ { emoji: "\u{1F4E7}", keywords: ["email", "e-mail", "mensagem", "contato"] },
796
+ { emoji: "\u{1F4CE}", keywords: ["anexo", "clipe", "arquivo"] },
797
+ { emoji: "\u{1F50D}", keywords: ["buscar", "procurar", "lupa", "pesquisa", "consulta"] },
798
+ { emoji: "\u{1F916}", keywords: ["robo", "rob\xF4", "bot", "assistente", "automatico", "autom\xE1tico"] },
799
+ { emoji: "\u{1F4AC}", keywords: ["mensagem", "conversa", "balao", "bal\xE3o", "chat"] }
800
+ ]
801
+ },
802
+ {
803
+ name: "Objetos",
804
+ entries: [
805
+ { emoji: "\u{1F381}", keywords: ["presente", "brinde", "surpresa"] },
806
+ { emoji: "\u{1F389}", keywords: ["festa", "comemorar", "parabens", "parab\xE9ns"] },
807
+ { emoji: "\u{1F38A}", keywords: ["festa", "confete", "comemorar"] },
808
+ { emoji: "\u{1F382}", keywords: ["bolo", "aniversario", "anivers\xE1rio", "festa"] },
809
+ { emoji: "\u{1F4A1}", keywords: ["ideia", "ide\xEDa", "lampada", "l\xE2mpada", "dica"] },
810
+ { emoji: "\u{1F514}", keywords: ["sino", "aviso", "notificacao", "notifica\xE7\xE3o", "lembrete"] },
811
+ { emoji: "\u2B50", keywords: ["estrela", "favorito", "avaliacao", "avalia\xE7\xE3o"] },
812
+ { emoji: "\u{1F525}", keywords: ["fogo", "quente", "destaque", "top"] },
813
+ { emoji: "\u{1F680}", keywords: ["foguete", "rapido", "r\xE1pido", "lancamento", "lan\xE7amento"] },
814
+ { emoji: "\u{1F4BB}", keywords: ["computador", "notebook", "trabalho"] },
815
+ { emoji: "\u{1F4F7}", keywords: ["foto", "camera", "c\xE2mera", "imagem"] },
816
+ { emoji: "\u{1F697}", keywords: ["carro", "veiculo", "ve\xEDculo", "automovel", "autom\xF3vel"] },
817
+ { emoji: "\u2708\uFE0F", keywords: ["aviao", "avi\xE3o", "viagem", "voo"] }
818
+ ]
819
+ },
820
+ {
821
+ name: "Comida",
822
+ entries: [
823
+ { emoji: "\u{1F354}", keywords: ["hamburguer", "hamb\xFArguer", "lanche", "comida"] },
824
+ { emoji: "\u{1F355}", keywords: ["pizza", "comida", "lanche"] },
825
+ { emoji: "\u{1F35F}", keywords: ["batata", "frita", "lanche"] },
826
+ { emoji: "\u{1F37F}", keywords: ["pipoca", "cinema", "filme"] },
827
+ { emoji: "\u{1F35E}", keywords: ["pao", "p\xE3o", "padaria"] },
828
+ { emoji: "\u{1F9C0}", keywords: ["queijo", "comida"] },
829
+ { emoji: "\u{1F957}", keywords: ["salada", "saudavel", "saud\xE1vel", "comida"] },
830
+ { emoji: "\u2615", keywords: ["cafe", "caf\xE9", "bebida", "quente"] },
831
+ { emoji: "\u{1F37A}", keywords: ["cerveja", "bebida", "chopp"] },
832
+ { emoji: "\u{1F377}", keywords: ["vinho", "bebida", "taca", "ta\xE7a"] },
833
+ { emoji: "\u{1F942}", keywords: ["brinde", "comemorar", "champanhe"] },
834
+ { emoji: "\u{1F964}", keywords: ["refrigerante", "bebida", "copo"] }
835
+ ]
836
+ }
837
+ ];
838
+ var ALL_ENTRIES = EMOJI_CATEGORIES.flatMap((category) => category.entries);
839
+ function normalize(value) {
840
+ return value.toLowerCase().normalize("NFD").replace(/\p{Diacritic}/gu, "").trim();
841
+ }
842
+ function searchEmojis(query) {
843
+ const term = normalize(query);
844
+ if (!term) return ALL_ENTRIES;
845
+ return ALL_ENTRIES.filter((entry) => entry.keywords.some((keyword) => normalize(keyword).startsWith(term)));
846
+ }
847
+
848
+ // src/EmojiPicker.tsx
849
+ import { useState as useState5, useCallback, useMemo as useMemo2 } from "react";
850
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
851
+ var DEFAULT_EMOJI_PICKER_LABELS = {
852
+ search: "Buscar emoji",
853
+ noResults: "Nenhum emoji encontrado"
854
+ };
855
+ var EmojiPicker = ({ onSelect, labels, className = "" }) => {
856
+ const searchLabel = labels?.search ?? DEFAULT_EMOJI_PICKER_LABELS.search;
857
+ const noResultsLabel = labels?.noResults ?? DEFAULT_EMOJI_PICKER_LABELS.noResults;
858
+ const [activeCategory, setActiveCategory] = useState5(0);
859
+ const [query, setQuery] = useState5("");
860
+ const handleSelect = useCallback(
861
+ (emoji) => {
862
+ onSelect(emoji);
863
+ },
864
+ [onSelect]
865
+ );
866
+ const isSearching = query.trim().length > 0;
867
+ const visibleEntries = useMemo2(
868
+ () => isSearching ? searchEmojis(query) : EMOJI_CATEGORIES[activeCategory].entries,
869
+ [isSearching, query, activeCategory]
870
+ );
871
+ return /* @__PURE__ */ jsxs7("div", { className: `bg-white border border-gray-200 rounded-lg shadow-lg overflow-hidden ${className}`, children: [
872
+ /* @__PURE__ */ jsx11("div", { className: "p-2 border-b border-gray-200", children: /* @__PURE__ */ jsx11(
873
+ "input",
874
+ {
875
+ type: "search",
876
+ value: query,
877
+ onChange: (event) => setQuery(event.target.value),
878
+ placeholder: searchLabel,
879
+ "aria-label": searchLabel,
880
+ className: "w-full rounded-md border border-gray-200 px-2 py-1.5 text-sm outline-none focus:border-blue-500"
881
+ }
882
+ ) }),
883
+ isSearching ? null : /* @__PURE__ */ jsx11("div", { className: "flex border-b border-gray-200 overflow-x-auto", children: EMOJI_CATEGORIES.map((category, index) => /* @__PURE__ */ jsxs7(
884
+ "button",
885
+ {
886
+ onClick: () => setActiveCategory(index),
887
+ className: `px-3 py-2 text-xs font-medium whitespace-nowrap border-b-2 transition-colors ${activeCategory === index ? "border-blue-500 text-blue-600" : "border-transparent text-gray-500 hover:text-gray-700"}`,
888
+ children: [
889
+ category.entries[0].emoji,
890
+ " ",
891
+ category.name
892
+ ]
893
+ },
894
+ category.name
895
+ )) }),
896
+ visibleEntries.length === 0 ? /* @__PURE__ */ jsx11("p", { className: "px-3 py-6 text-center text-sm text-gray-500", children: noResultsLabel }) : /* @__PURE__ */ jsx11("div", { className: "grid grid-cols-10 gap-0.5 p-2 max-h-[240px] overflow-y-auto", children: visibleEntries.map((entry) => /* @__PURE__ */ jsx11(
897
+ "button",
898
+ {
899
+ onClick: () => handleSelect(entry.emoji),
900
+ className: "w-8 h-8 flex items-center justify-center text-lg hover:bg-gray-100 rounded transition-colors cursor-pointer",
901
+ "aria-label": entry.emoji,
902
+ title: entry.keywords[0],
903
+ children: entry.emoji
904
+ },
905
+ entry.emoji
906
+ )) })
907
+ ] });
908
+ };
909
+
910
+ // src/MessageComposer.tsx
911
+ import { useState as useState6, useRef as useRef2, useCallback as useCallback2 } from "react";
912
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
913
+ function applyQuickReplyVariables(template, variables = {}) {
914
+ return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, chave) => variables[chave] ?? "");
915
+ }
916
+ function resolveQuickReply(quickReply, variables = {}) {
917
+ return typeof quickReply.text === "function" ? quickReply.text(variables) : applyQuickReplyVariables(quickReply.text, variables);
918
+ }
919
+ var DEFAULT_MESSAGE_COMPOSER_LABELS = {
920
+ emoji: "Emoji",
921
+ attach: "Anexar",
922
+ send: "Enviar"
923
+ };
924
+ var DEFAULT_ACCEPTED_FILE_TYPES = [
925
+ "image/jpeg,image/png,image/webp",
926
+ "audio/aac,audio/mp4,audio/mpeg,audio/amr,audio/ogg",
927
+ "video/mp4,video/3gpp",
928
+ "application/pdf,text/plain,text/csv",
929
+ "application/msword,application/vnd.ms-excel,application/vnd.ms-powerpoint",
930
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
931
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
932
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation"
933
+ ].join(",");
934
+ var MessageComposer = ({
935
+ onSend,
936
+ onAttach,
937
+ value: externalValue,
938
+ onChange: externalOnChange,
939
+ quickReplies,
940
+ quickReplyVariables,
941
+ features,
942
+ placeholder = "Digite uma mensagem...",
943
+ maxLength,
944
+ disabled = false,
945
+ acceptedFileTypes = DEFAULT_ACCEPTED_FILE_TYPES,
946
+ idleAction,
947
+ className,
948
+ classNames,
949
+ labels
950
+ }) => {
951
+ const emojiLabel = labels?.emoji ?? DEFAULT_MESSAGE_COMPOSER_LABELS.emoji;
952
+ const attachLabel = labels?.attach ?? DEFAULT_MESSAGE_COMPOSER_LABELS.attach;
953
+ const sendLabel = labels?.send ?? DEFAULT_MESSAGE_COMPOSER_LABELS.send;
954
+ const [internalText, setInternalText] = useState6("");
955
+ const [showEmoji, setShowEmoji] = useState6(false);
956
+ const [attachments, setAttachments] = useState6([]);
957
+ const textareaRef = useRef2(null);
958
+ const fileInputRef = useRef2(null);
959
+ const isControlled = externalValue !== void 0;
960
+ const text = isControlled ? externalValue : internalText;
961
+ const setText = useCallback2((newText) => {
962
+ if (isControlled) {
963
+ externalOnChange?.(newText);
964
+ } else {
965
+ setInternalText(newText);
966
+ }
967
+ }, [isControlled, externalOnChange]);
968
+ const showEmojiButton = features?.emoji !== false;
969
+ const showAttachButton = features?.documents !== false;
970
+ const sendMessage = useCallback2(() => {
971
+ const trimmed = text.trim();
972
+ if (!trimmed && attachments.length === 0) return;
973
+ if (trimmed) onSend(trimmed);
974
+ for (const a of attachments) {
975
+ onAttach?.(a.file);
976
+ URL.revokeObjectURL(a.previewUrl);
977
+ }
978
+ if (!isControlled) setInternalText("");
979
+ setAttachments([]);
980
+ setShowEmoji(false);
981
+ if (textareaRef.current) textareaRef.current.style.height = "auto";
982
+ }, [text, attachments, onSend, onAttach, isControlled]);
983
+ const handleKeyDown = useCallback2((e) => {
984
+ if (e.key === "Enter" && !e.shiftKey) {
985
+ e.preventDefault();
986
+ if (!disabled) sendMessage();
987
+ }
988
+ }, [sendMessage, disabled]);
989
+ const handleInput = useCallback2(() => {
990
+ const ta = textareaRef.current;
991
+ if (!ta) return;
992
+ ta.style.height = "auto";
993
+ ta.style.height = `${Math.min(ta.scrollHeight, 100)}px`;
994
+ }, []);
995
+ const handleEmojiSelect = useCallback2((emoji) => {
996
+ const ta = textareaRef.current;
997
+ if (!ta) {
998
+ setText(text + emoji);
999
+ return;
1000
+ }
1001
+ const start = ta.selectionStart;
1002
+ const end = ta.selectionEnd;
1003
+ const newText = text.slice(0, start) + emoji + text.slice(end);
1004
+ setText(newText);
1005
+ requestAnimationFrame(() => {
1006
+ ta.focus();
1007
+ ta.setSelectionRange(start + emoji.length, start + emoji.length);
1008
+ handleInput();
1009
+ });
1010
+ }, [text, setText, handleInput]);
1011
+ const handleFileChange = useCallback2((e) => {
1012
+ const files = e.target.files;
1013
+ if (!files) return;
1014
+ const previews = [];
1015
+ for (let i = 0; i < files.length; i++) {
1016
+ const file = files[i];
1017
+ previews.push({ file, previewUrl: file.type.startsWith("image/") ? URL.createObjectURL(file) : "" });
1018
+ }
1019
+ setAttachments((prev) => [...prev, ...previews]);
1020
+ if (fileInputRef.current) fileInputRef.current.value = "";
1021
+ }, []);
1022
+ const removeAttachment = useCallback2((index) => {
1023
+ setAttachments((prev) => {
1024
+ const next = [...prev];
1025
+ if (next[index].previewUrl) URL.revokeObjectURL(next[index].previewUrl);
1026
+ next.splice(index, 1);
1027
+ return next;
1028
+ });
1029
+ }, []);
1030
+ const insertFormatting = useCallback2((marker) => {
1031
+ const ta = textareaRef.current;
1032
+ if (!ta) return;
1033
+ const start = ta.selectionStart;
1034
+ const end = ta.selectionEnd;
1035
+ const sel = text.slice(start, end);
1036
+ if (sel) {
1037
+ setText(text.slice(0, start) + marker + sel + marker + text.slice(end));
1038
+ requestAnimationFrame(() => {
1039
+ ta.focus();
1040
+ ta.setSelectionRange(start + marker.length + sel.length + marker.length, start + marker.length + sel.length + marker.length);
1041
+ });
1042
+ }
1043
+ }, [text, setText]);
1044
+ const canSend = text.trim().length > 0 || attachments.length > 0;
1045
+ const remaining = maxLength ? maxLength - text.length : null;
1046
+ return (
1047
+ /* A barra é a superfície (cinza, largura cheia, sem raio) e o campo dentro é que arredonda —
1048
+ ordem do WhatsApp. Invertido, o pill arredondado ia até a borda da tela e os cantos
1049
+ descobriam o fundo branco da página, que lia como defeito. */
1050
+ /* @__PURE__ */ jsxs8("div", { className: cn("bg-[#f0f2f5] px-2 py-2", className), children: [
1051
+ quickReplies && quickReplies.length > 0 && /* @__PURE__ */ jsx12(
1052
+ "div",
1053
+ {
1054
+ className: cn(
1055
+ "mb-2 flex flex-nowrap gap-1 overflow-x-auto px-1 sm:flex-wrap sm:overflow-x-visible [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
1056
+ classNames?.quickReplies
1057
+ ),
1058
+ children: quickReplies.map((quickReply) => /* @__PURE__ */ jsx12(
1059
+ "button",
1060
+ {
1061
+ type: "button",
1062
+ onClick: () => {
1063
+ setText(resolveQuickReply(quickReply, quickReplyVariables));
1064
+ textareaRef.current?.focus();
1065
+ },
1066
+ className: cn(
1067
+ "whitespace-nowrap rounded-full border border-gray-200 bg-white px-2 py-1 text-xs text-gray-600 transition-colors hover:border-teal-200 hover:bg-teal-50 hover:text-teal-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400",
1068
+ classNames?.quickReply
1069
+ ),
1070
+ children: quickReply.label
1071
+ },
1072
+ quickReply.key
1073
+ ))
1074
+ }
1075
+ ),
1076
+ attachments.length > 0 && /* @__PURE__ */ jsx12("div", { className: "flex gap-2 px-1 pb-2 overflow-x-auto", children: attachments.map((a, i) => /* @__PURE__ */ jsxs8("div", { className: "relative flex-shrink-0", children: [
1077
+ a.previewUrl ? /* @__PURE__ */ jsx12("img", { src: a.previewUrl, alt: "", className: "w-16 h-16 object-cover rounded-lg border border-gray-200" }) : /* @__PURE__ */ jsx12("div", { className: "w-16 h-16 bg-gray-100 rounded-lg border border-gray-200 flex items-center justify-center", children: /* @__PURE__ */ jsxs8("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#9ca3af", strokeWidth: "1.5", children: [
1078
+ /* @__PURE__ */ jsx12("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1079
+ /* @__PURE__ */ jsx12("polyline", { points: "14 2 14 8 20 8" })
1080
+ ] }) }),
1081
+ /* @__PURE__ */ jsx12("button", { onClick: () => removeAttachment(i), className: "absolute -top-2 -right-2 w-5 h-5 bg-gray-600 text-white rounded-full flex items-center justify-center hover:bg-gray-800 text-xs", children: "\u2715" })
1082
+ ] }, i)) }),
1083
+ /* @__PURE__ */ jsxs8("div", { className: cn("flex items-end gap-1.5 rounded-xl bg-white px-3 py-2", classNames?.field), children: [
1084
+ showEmojiButton && /* @__PURE__ */ jsxs8("div", { className: "relative flex-shrink-0", children: [
1085
+ /* @__PURE__ */ jsx12("button", { onClick: () => setShowEmoji((v) => !v), className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 transition-colors", "aria-label": emojiLabel, children: /* @__PURE__ */ jsxs8("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1086
+ /* @__PURE__ */ jsx12("circle", { cx: "12", cy: "12", r: "10" }),
1087
+ /* @__PURE__ */ jsx12("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
1088
+ /* @__PURE__ */ jsx12("circle", { cx: "9", cy: "9", r: "0.5", fill: "currentColor" }),
1089
+ /* @__PURE__ */ jsx12("circle", { cx: "15", cy: "9", r: "0.5", fill: "currentColor" })
1090
+ ] }) }),
1091
+ showEmoji && /* @__PURE__ */ jsx12("div", { className: "absolute bottom-full left-0 mb-2 z-10", children: /* @__PURE__ */ jsx12(EmojiPicker, { onSelect: handleEmojiSelect }) })
1092
+ ] }),
1093
+ /* @__PURE__ */ jsx12(
1094
+ "textarea",
1095
+ {
1096
+ ref: textareaRef,
1097
+ value: text,
1098
+ onChange: (e) => {
1099
+ setText(e.target.value);
1100
+ handleInput();
1101
+ },
1102
+ onKeyDown: handleKeyDown,
1103
+ placeholder,
1104
+ rows: 1,
1105
+ disabled,
1106
+ className: "flex-1 resize-none bg-transparent text-[15px] text-[#3b4a54] placeholder-[#8696a0] outline-none py-1.5 max-h-[100px] leading-relaxed",
1107
+ style: { fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" }
1108
+ }
1109
+ ),
1110
+ showAttachButton && /* @__PURE__ */ jsxs8(Fragment2, { children: [
1111
+ /* @__PURE__ */ jsx12("input", { ref: fileInputRef, type: "file", multiple: true, accept: acceptedFileTypes, onChange: handleFileChange, className: "hidden" }),
1112
+ /* @__PURE__ */ jsx12("button", { onClick: () => fileInputRef.current?.click(), className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 flex-shrink-0 transition-colors", "aria-label": attachLabel, children: /* @__PURE__ */ jsx12("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx12("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) }) })
1113
+ ] }),
1114
+ !canSend && idleAction ? /* @__PURE__ */ jsx12("div", { className: "flex-shrink-0", children: idleAction }) : /* @__PURE__ */ jsx12(
1115
+ "button",
1116
+ {
1117
+ onClick: sendMessage,
1118
+ disabled: !canSend || disabled,
1119
+ className: `w-10 h-10 flex items-center justify-center rounded-full flex-shrink-0 transition-all ${canSend && !disabled ? "bg-[#00a884] text-white hover:bg-[#06cf9c] shadow-sm" : "bg-gray-200 text-gray-400 cursor-not-allowed"}`,
1120
+ "aria-label": sendLabel,
1121
+ children: /* @__PURE__ */ jsx12("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx12("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
1122
+ }
1123
+ )
1124
+ ] }),
1125
+ remaining !== null && /* @__PURE__ */ jsx12("div", { className: "flex justify-end mt-1 pr-1", children: /* @__PURE__ */ jsx12("span", { className: `text-xs ${remaining < 20 ? "text-red-500" : "text-gray-400"}`, children: remaining }) })
1126
+ ] })
1127
+ );
1128
+ };
1129
+
1130
+ // src/AudioRecorderButton.tsx
1131
+ import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef3, useState as useState7 } from "react";
1132
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
1133
+ var DEFAULT_AUDIO_RECORDER_BUTTON_LABELS = {
1134
+ start: "Gravar \xE1udio",
1135
+ stop: "Parar grava\xE7\xE3o",
1136
+ unsupported: "Este navegador n\xE3o grava \xE1udio.",
1137
+ denied: "Sem permiss\xE3o para usar o microfone.",
1138
+ review: "Ou\xE7a antes de enviar",
1139
+ send: "Enviar \xE1udio",
1140
+ discard: "Descartar \xE1udio",
1141
+ empty: "Nada foi captado pelo microfone."
1142
+ };
1143
+ var DEFAULT_MAX_RECORDING_MILLISECONDS = 5 * 60 * 1e3;
1144
+ var RECORDING_FORMATS = [
1145
+ { mimeType: "audio/ogg;codecs=opus", uploadMimeType: "audio/ogg", extension: "ogg" },
1146
+ { mimeType: "audio/mp4", uploadMimeType: "audio/mp4", extension: "m4a" },
1147
+ { mimeType: "audio/webm", uploadMimeType: "audio/webm", extension: "webm" }
1148
+ ];
1149
+ function resolveRecordingFormat() {
1150
+ if (typeof MediaRecorder === "undefined") return void 0;
1151
+ if (typeof MediaRecorder.isTypeSupported !== "function") return RECORDING_FORMATS[0];
1152
+ return RECORDING_FORMATS.find((format) => MediaRecorder.isTypeSupported(format.mimeType));
1153
+ }
1154
+ function AudioRecorderButton({
1155
+ onRecorded,
1156
+ onFailure,
1157
+ onRecordingChange,
1158
+ reviewBeforeSend = true,
1159
+ maxDurationMilliseconds = DEFAULT_MAX_RECORDING_MILLISECONDS,
1160
+ labels,
1161
+ disabled
1162
+ }) {
1163
+ const labelOf = (key) => labels?.[key] ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS[key];
1164
+ const [isRecording, setIsRecording] = useState7(false);
1165
+ const [pending, setPending] = useState7(void 0);
1166
+ const recorderRef = useRef3(null);
1167
+ const autoStopRef = useRef3(void 0);
1168
+ const discard = useCallback3(() => {
1169
+ setPending((current) => {
1170
+ if (current) URL.revokeObjectURL(current.objectURL);
1171
+ return void 0;
1172
+ });
1173
+ }, []);
1174
+ useEffect2(() => discard, [discard]);
1175
+ const confirm = useCallback3(() => {
1176
+ if (!pending) return;
1177
+ void onRecorded(pending.file);
1178
+ discard();
1179
+ }, [discard, onRecorded, pending]);
1180
+ const stop = useCallback3(() => {
1181
+ recorderRef.current?.stop();
1182
+ }, []);
1183
+ const start = useCallback3(async () => {
1184
+ const format = resolveRecordingFormat();
1185
+ if (!format || !navigator.mediaDevices?.getUserMedia) {
1186
+ onFailure?.(labels?.unsupported ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.unsupported);
1187
+ return;
1188
+ }
1189
+ try {
1190
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
1191
+ const recorder = new MediaRecorder(stream, { mimeType: format.mimeType });
1192
+ const chunks = [];
1193
+ recorder.addEventListener("dataavailable", (event) => {
1194
+ if (event.data.size > 0) chunks.push(event.data);
1195
+ });
1196
+ recorder.addEventListener("stop", () => {
1197
+ stream.getTracks().forEach((track) => track.stop());
1198
+ clearTimeout(autoStopRef.current);
1199
+ setIsRecording(false);
1200
+ onRecordingChange?.(false);
1201
+ recorderRef.current = null;
1202
+ const blob = new Blob(chunks, { type: format.uploadMimeType });
1203
+ const file = new File([blob], `audio-${Date.now()}.${format.extension}`, {
1204
+ type: format.uploadMimeType
1205
+ });
1206
+ if (blob.size === 0) {
1207
+ onFailure?.(labelOf("empty"));
1208
+ return;
1209
+ }
1210
+ if (!reviewBeforeSend) {
1211
+ void onRecorded(file);
1212
+ return;
1213
+ }
1214
+ setPending({ file, objectURL: URL.createObjectURL(blob) });
1215
+ });
1216
+ recorderRef.current = recorder;
1217
+ recorder.start();
1218
+ autoStopRef.current = setTimeout(() => recorder.stop(), maxDurationMilliseconds);
1219
+ setIsRecording(true);
1220
+ onRecordingChange?.(true);
1221
+ } catch {
1222
+ onFailure?.(labels?.denied ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.denied);
1223
+ }
1224
+ }, [maxDurationMilliseconds, onFailure, onRecorded, onRecordingChange, reviewBeforeSend]);
1225
+ const toggleLabel = isRecording ? labelOf("stop") : labelOf("start");
1226
+ return (
1227
+ /* O painel de revisão flutua sobre o botão em vez de ocupar espaço na barra: o microfone mora
1228
+ na caixa do botão de enviar, e empurrar o composer para cima a cada gravação faria a
1229
+ conversa saltar. */
1230
+ /* @__PURE__ */ jsxs9("div", { className: "relative flex-shrink-0", children: [
1231
+ pending && /* @__PURE__ */ jsxs9(
1232
+ "div",
1233
+ {
1234
+ role: "group",
1235
+ "aria-label": labelOf("review"),
1236
+ className: "absolute bottom-full right-0 z-20 mb-2 flex w-[calc(100vw-2rem)] max-w-[20rem] flex-wrap items-center justify-end gap-2 rounded-xl border border-gray-200 bg-white p-2 shadow-lg dark:border-gray-700 dark:bg-gray-800",
1237
+ children: [
1238
+ /* @__PURE__ */ jsx13("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx13(AudioPlayer, { src: pending.objectURL }) }),
1239
+ /* @__PURE__ */ jsx13(
1240
+ "button",
1241
+ {
1242
+ type: "button",
1243
+ onClick: discard,
1244
+ title: labelOf("discard"),
1245
+ "aria-label": labelOf("discard"),
1246
+ className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full text-gray-500 transition-colors hover:bg-gray-100 hover:text-red-500 dark:hover:bg-gray-700",
1247
+ children: /* @__PURE__ */ jsxs9("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
1248
+ /* @__PURE__ */ jsx13("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1249
+ /* @__PURE__ */ jsx13("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1250
+ ] })
1251
+ }
1252
+ ),
1253
+ /* @__PURE__ */ jsx13(
1254
+ "button",
1255
+ {
1256
+ type: "button",
1257
+ onClick: confirm,
1258
+ title: labelOf("send"),
1259
+ "aria-label": labelOf("send"),
1260
+ className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-emerald-500 text-white transition-colors hover:bg-emerald-600",
1261
+ children: /* @__PURE__ */ jsx13("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx13("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
1262
+ }
1263
+ )
1264
+ ]
1265
+ }
1266
+ ),
1267
+ /* @__PURE__ */ jsx13(
1268
+ "button",
1269
+ {
1270
+ type: "button",
1271
+ disabled: disabled || pending !== void 0,
1272
+ onClick: () => isRecording ? stop() : void start(),
1273
+ title: toggleLabel,
1274
+ "aria-label": toggleLabel,
1275
+ "aria-pressed": isRecording,
1276
+ className: `flex h-10 w-10 items-center justify-center rounded-full transition-colors disabled:opacity-50 ${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"}`,
1277
+ children: isRecording ? /* @__PURE__ */ jsx13("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx13("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) }) : /* @__PURE__ */ jsxs9("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
1278
+ /* @__PURE__ */ jsx13("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z" }),
1279
+ /* @__PURE__ */ jsx13("path", { d: "M19 11a7 7 0 0 1-14 0" }),
1280
+ /* @__PURE__ */ jsx13("line", { x1: "12", y1: "18", x2: "12", y2: "22" })
1281
+ ] })
1282
+ }
1283
+ )
1284
+ ] })
1285
+ );
1286
+ }
1287
+
1288
+ // src/DateDivider.tsx
1289
+ import { jsx as jsx14 } from "react/jsx-runtime";
1290
+ function DateDivider({ iso, className, classNames }) {
1291
+ const { dateDivider } = useConversationLocales();
1292
+ return /* @__PURE__ */ jsx14("div", { className: cn("flex justify-center sticky top-0 z-10 my-2 pointer-events-none", classNames?.root, className), children: /* @__PURE__ */ jsx14(
1293
+ "span",
1294
+ {
1295
+ className: cn(
1296
+ "bg-white/95 dark:bg-gray-800/95 text-gray-600 dark:text-gray-300 text-xs font-medium px-3 py-1 rounded-lg shadow-sm",
1297
+ classNames?.label
1298
+ ),
1299
+ children: formatDividerLabel(iso, dateDivider)
1300
+ }
1301
+ ) });
1302
+ }
1303
+ function formatDividerLabel(iso, dateDivider) {
1304
+ const date = new Date(iso);
1305
+ const today = /* @__PURE__ */ new Date();
1306
+ const yesterday = new Date(today);
1307
+ yesterday.setDate(today.getDate() - 1);
1308
+ if (isSameDay(date, today)) return dateDivider.today;
1309
+ if (isSameDay(date, yesterday)) return dateDivider.yesterday;
1310
+ return date.toLocaleDateString("pt-BR", { day: "2-digit", month: "long", year: "numeric" });
1311
+ }
1312
+
1313
+ // src/lib/phone.ts
1314
+ function formatPhone(number) {
1315
+ const digits = number.replace(/\D/g, "");
1316
+ if (digits.length === 13) {
1317
+ return `+${digits.slice(0, 2)} (${digits.slice(2, 4)}) ${digits.slice(4, 9)}-${digits.slice(9, 13)}`;
1318
+ }
1319
+ if (digits.length === 12) {
1320
+ return `+${digits.slice(0, 2)} (${digits.slice(2, 4)}) ${digits.slice(4, 8)}-${digits.slice(8, 12)}`;
1321
+ }
1322
+ if (digits.length === 11) {
1323
+ return `(${digits.slice(0, 2)}) ${digits.slice(2, 7)}-${digits.slice(7, 11)}`;
1324
+ }
1325
+ if (digits.length === 10) {
1326
+ return `(${digits.slice(0, 2)}) ${digits.slice(2, 6)}-${digits.slice(6, 10)}`;
1327
+ }
1328
+ return number;
1329
+ }
1330
+ var COUNTRY_FLAG_BY_DIAL_CODE = {
1331
+ "55": "\u{1F1E7}\u{1F1F7}",
1332
+ "351": "\u{1F1F5}\u{1F1F9}",
1333
+ "34": "\u{1F1EA}\u{1F1F8}",
1334
+ "54": "\u{1F1E6}\u{1F1F7}",
1335
+ "56": "\u{1F1E8}\u{1F1F1}",
1336
+ "57": "\u{1F1E8}\u{1F1F4}",
1337
+ "52": "\u{1F1F2}\u{1F1FD}",
1338
+ "598": "\u{1F1FA}\u{1F1FE}",
1339
+ "595": "\u{1F1F5}\u{1F1FE}",
1340
+ "44": "\u{1F1EC}\u{1F1E7}",
1341
+ "49": "\u{1F1E9}\u{1F1EA}",
1342
+ "39": "\u{1F1EE}\u{1F1F9}",
1343
+ "33": "\u{1F1EB}\u{1F1F7}"
1344
+ };
1345
+ function phoneCountryFlag(number) {
1346
+ const digits = number.replace(/\D/g, "");
1347
+ for (const length of [3, 2, 1]) {
1348
+ const flag = COUNTRY_FLAG_BY_DIAL_CODE[digits.slice(0, length)];
1349
+ if (flag) return flag;
1350
+ }
1351
+ return "";
1352
+ }
1353
+ function phoneInitials(number) {
1354
+ const digits = number.replace(/\D/g, "");
1355
+ return digits.slice(-2);
1356
+ }
1357
+
1358
+ // src/hooks/useAsyncResource.ts
1359
+ import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4, useState as useState8 } from "react";
1360
+ function useAsyncResource(fetcher, deps) {
1361
+ const [data, setData] = useState8(void 0);
1362
+ const [loading, setLoading] = useState8(false);
1363
+ const [error, setError] = useState8(void 0);
1364
+ const requestIdRef = useRef4(0);
1365
+ const load = useCallback4(async () => {
1366
+ const requestId = ++requestIdRef.current;
1367
+ setLoading(true);
1368
+ setError(void 0);
1369
+ try {
1370
+ const result = await fetcher();
1371
+ if (requestId === requestIdRef.current) setData(result);
1372
+ } catch (err) {
1373
+ if (requestId === requestIdRef.current) setError(err instanceof Error ? err : new Error(String(err)));
1374
+ } finally {
1375
+ if (requestId === requestIdRef.current) setLoading(false);
1376
+ }
1377
+ }, deps);
1378
+ useEffect3(() => {
1379
+ load();
1380
+ }, [load]);
1381
+ return { data, loading, error, refetch: load };
1382
+ }
1383
+
1384
+ // src/lib/paginated.ts
1385
+ function conversationsOf(result) {
1386
+ return Array.isArray(result) ? result : result.conversations;
1387
+ }
1388
+ function documentsOf(result) {
1389
+ return Array.isArray(result) ? result : result.documents;
1390
+ }
1391
+ function totalOf(result) {
1392
+ return Array.isArray(result) ? result.length : result.total;
1393
+ }
1394
+
1395
+ // src/hooks/useConversationDocuments.ts
1396
+ function useConversationDocuments(conversationId, params) {
1397
+ const context = useConversations();
1398
+ if (!context) {
1399
+ throw new Error("useConversationDocuments requires an ancestor <ConversationsProvider>");
1400
+ }
1401
+ const { api } = context;
1402
+ const { data, loading, error, refetch } = useAsyncResource(
1403
+ () => conversationId ? api.getDocuments(conversationId, params) : Promise.resolve([]),
1404
+ [conversationId, params?.search, params?.page, params?.limit, params?.source, params?.sortDirection]
1405
+ );
1406
+ if (data === void 0) {
1407
+ return { documents: [], total: 0, loading, error, refetch };
1408
+ }
1409
+ return { documents: documentsOf(data), total: totalOf(data), loading, error, refetch };
1410
+ }
1411
+
1412
+ // src/ConversationDocumentsPanel.tsx
1413
+ import { useState as useState9 } from "react";
1414
+ import { ArrowUpDown, Bot, Download, Eye, Users } from "lucide-react";
1415
+ import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1416
+ var DOCUMENT_SOURCE_FILTER = {
1417
+ ALL: "all",
1418
+ CUSTOMER: "customer",
1419
+ TEAM: "team"
1420
+ };
1421
+ var TEAM_SOURCES = /* @__PURE__ */ new Set(["agent", "bot"]);
1422
+ var DEFAULT_CONVERSATION_DOCUMENTS_LABELS = {
1423
+ toggle: "\u{1F4CE} Arquivos",
1424
+ title: "Arquivos da conversa",
1425
+ noResults: "Nenhum arquivo encontrado para os filtros aplicados",
1426
+ view: "Visualizar",
1427
+ sourceFilterAll: "Todas as origens",
1428
+ sourceFilterCustomer: "Cliente",
1429
+ sourceFilterTeam: "Equipe",
1430
+ sortMostRecent: "Mais recentes",
1431
+ sortOldest: "Mais antigos",
1432
+ clearFilters: "Limpar filtros",
1433
+ selectAll: "Selecionar todos desta p\xE1gina",
1434
+ downloadSelected: (count) => `Baixar ${count} selecionado${count === 1 ? "" : "s"} (.zip)`,
1435
+ archiveFailed: "N\xE3o foi poss\xEDvel montar o arquivo compactado.",
1436
+ total: (count) => `${count} arquivo${count === 1 ? "" : "s"}`,
1437
+ page: (current, last) => `${current} / ${last}`,
1438
+ searchPlaceholder: "Buscar por nome do arquivo",
1439
+ empty: "Nenhum arquivo nesta conversa.",
1440
+ loading: "Carregando arquivos\u2026",
1441
+ failure: "N\xE3o foi poss\xEDvel carregar os arquivos.",
1442
+ download: "Baixar"
1443
+ };
1444
+ var DEFAULT_PER_PAGE = 10;
1445
+ function ConversationDocumentsPanel({
1446
+ conversationId,
1447
+ open,
1448
+ perPage = DEFAULT_PER_PAGE,
1449
+ labels: labelsOverride,
1450
+ className,
1451
+ classNames
1452
+ }) {
1453
+ const labels = { ...DEFAULT_CONVERSATION_DOCUMENTS_LABELS, ...labelsOverride };
1454
+ const context = useConversations();
1455
+ const [search, setSearch] = useState9("");
1456
+ const [sourceFilter, setSourceFilter] = useState9(DOCUMENT_SOURCE_FILTER.ALL);
1457
+ const [sortDirection, setSortDirection] = useState9("desc");
1458
+ const [page, setPage] = useState9(1);
1459
+ const [selectedIds, setSelectedIds] = useState9([]);
1460
+ const [archiveError, setArchiveError] = useState9(false);
1461
+ const hasFilters = search !== "" || sourceFilter !== DOCUMENT_SOURCE_FILTER.ALL || sortDirection !== "desc";
1462
+ const { documents, total, loading, error } = useConversationDocuments(open ? conversationId : void 0, {
1463
+ search,
1464
+ page,
1465
+ limit: perPage,
1466
+ sortDirection,
1467
+ ...sourceFilter === DOCUMENT_SOURCE_FILTER.ALL ? {} : { source: sourceFilter }
1468
+ });
1469
+ const lastPage = Math.max(1, Math.ceil(total / perPage));
1470
+ function applyFilter(change) {
1471
+ change();
1472
+ setPage(1);
1473
+ }
1474
+ async function handleOpen(uploadId, disposition) {
1475
+ const url = await context?.api.getDocumentUrl(uploadId, disposition);
1476
+ if (url) window.open(url, "_blank", "noopener,noreferrer");
1477
+ }
1478
+ const canArchive = typeof context?.api.downloadDocumentsArchive === "function";
1479
+ const pageIds = documents.map((document2) => document2.id);
1480
+ const allOnPageSelected = pageIds.length > 0 && pageIds.every((id) => selectedIds.includes(id));
1481
+ function toggleSelected(uploadId) {
1482
+ setArchiveError(false);
1483
+ setSelectedIds(
1484
+ (current) => current.includes(uploadId) ? current.filter((id) => id !== uploadId) : [...current, uploadId]
1485
+ );
1486
+ }
1487
+ function toggleAllOnPage() {
1488
+ setArchiveError(false);
1489
+ setSelectedIds(
1490
+ (current) => allOnPageSelected ? current.filter((id) => !pageIds.includes(id)) : [...current, ...pageIds.filter((id) => !current.includes(id))]
1491
+ );
1492
+ }
1493
+ async function handleDownloadSelected() {
1494
+ const archive = context?.api.downloadDocumentsArchive;
1495
+ if (!archive || selectedIds.length === 0) return;
1496
+ setArchiveError(false);
1497
+ try {
1498
+ const blob = await archive(conversationId, selectedIds);
1499
+ const url = URL.createObjectURL(blob);
1500
+ const anchor = document.createElement("a");
1501
+ anchor.href = url;
1502
+ anchor.download = `conversa-${conversationId}.zip`;
1503
+ anchor.click();
1504
+ URL.revokeObjectURL(url);
1505
+ setSelectedIds([]);
1506
+ } catch {
1507
+ setArchiveError(true);
1508
+ }
1509
+ }
1510
+ if (!open) return null;
1511
+ return /* @__PURE__ */ jsx15("div", { className: cn("border-b", classNames?.root, className), children: /* @__PURE__ */ jsxs10("section", { className: cn("px-4 py-3", classNames?.body), children: [
1512
+ /* @__PURE__ */ jsx15("p", { className: cn("mb-2 text-sm font-medium", classNames?.title), children: labels.title }),
1513
+ /* @__PURE__ */ jsxs10("div", { className: cn("mb-2 flex flex-wrap items-center gap-2 border-b pb-2", classNames?.filters), children: [
1514
+ /* @__PURE__ */ jsx15(
1515
+ "input",
1516
+ {
1517
+ type: "search",
1518
+ value: search,
1519
+ onChange: (event) => applyFilter(() => setSearch(event.target.value)),
1520
+ placeholder: labels.searchPlaceholder,
1521
+ "aria-label": labels.searchPlaceholder,
1522
+ className: cn("w-full rounded-md border px-3 py-2 text-sm sm:w-52", classNames?.search)
1523
+ }
1524
+ ),
1525
+ /* @__PURE__ */ jsxs10(
1526
+ "select",
1527
+ {
1528
+ value: sourceFilter,
1529
+ onChange: (event) => applyFilter(() => setSourceFilter(event.target.value)),
1530
+ "aria-label": labels.sourceFilterAll,
1531
+ className: cn("w-full rounded-md border px-2 py-2 text-sm sm:w-36", classNames?.sourceSelect),
1532
+ children: [
1533
+ /* @__PURE__ */ jsx15("option", { value: DOCUMENT_SOURCE_FILTER.ALL, children: labels.sourceFilterAll }),
1534
+ /* @__PURE__ */ jsx15("option", { value: DOCUMENT_SOURCE_FILTER.CUSTOMER, children: labels.sourceFilterCustomer }),
1535
+ /* @__PURE__ */ jsx15("option", { value: DOCUMENT_SOURCE_FILTER.TEAM, children: labels.sourceFilterTeam })
1536
+ ]
1537
+ }
1538
+ ),
1539
+ /* @__PURE__ */ jsxs10(
1540
+ "button",
1541
+ {
1542
+ type: "button",
1543
+ onClick: () => applyFilter(() => setSortDirection(sortDirection === "desc" ? "asc" : "desc")),
1544
+ className: cn("cv-header-action inline-flex items-center gap-1", classNames?.sortButton),
1545
+ children: [
1546
+ /* @__PURE__ */ jsx15(ArrowUpDown, { size: 14 }),
1547
+ sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest
1548
+ ]
1549
+ }
1550
+ ),
1551
+ hasFilters ? /* @__PURE__ */ jsx15(
1552
+ "button",
1553
+ {
1554
+ type: "button",
1555
+ onClick: () => applyFilter(() => {
1556
+ setSearch("");
1557
+ setSourceFilter(DOCUMENT_SOURCE_FILTER.ALL);
1558
+ setSortDirection("desc");
1559
+ }),
1560
+ className: cn("cv-header-action", classNames?.clearButton),
1561
+ children: labels.clearFilters
1562
+ }
1563
+ ) : null
1564
+ ] }),
1565
+ loading ? /* @__PURE__ */ jsx15("p", { className: cn("text-xs text-gray-500", classNames?.status), children: labels.loading }) : null,
1566
+ error ? /* @__PURE__ */ jsx15("p", { role: "alert", className: cn("text-xs text-red-600 dark:text-red-400", classNames?.status), children: labels.failure }) : null,
1567
+ !loading && !error && documents.length === 0 ? /* @__PURE__ */ jsx15("p", { className: cn("text-xs text-gray-500", classNames?.status), children: hasFilters ? labels.noResults : labels.empty }) : null,
1568
+ canArchive && documents.length > 0 ? /* @__PURE__ */ jsxs10("div", { className: cn("mb-2 flex flex-wrap items-center gap-3 text-xs", classNames?.selectionBar), children: [
1569
+ /* @__PURE__ */ jsxs10("label", { className: "inline-flex items-center gap-1.5", children: [
1570
+ /* @__PURE__ */ jsx15(
1571
+ "input",
1572
+ {
1573
+ type: "checkbox",
1574
+ checked: allOnPageSelected,
1575
+ onChange: toggleAllOnPage,
1576
+ className: cn(classNames?.checkbox)
1577
+ }
1578
+ ),
1579
+ labels.selectAll
1580
+ ] }),
1581
+ selectedIds.length > 0 ? /* @__PURE__ */ jsx15("button", { type: "button", onClick: () => void handleDownloadSelected(), className: "cv-header-action", children: labels.downloadSelected(selectedIds.length) }) : null,
1582
+ archiveError ? /* @__PURE__ */ jsx15("span", { role: "alert", className: "text-red-600 dark:text-red-400", children: labels.archiveFailed }) : null
1583
+ ] }) : null,
1584
+ /* @__PURE__ */ jsx15("ul", { className: cn("space-y-2", classNames?.list), children: documents.map((document2) => {
1585
+ const isFromCustomer = !TEAM_SOURCES.has(document2.source);
1586
+ const SourceIcon = isFromCustomer ? Users : Bot;
1587
+ return /* @__PURE__ */ jsxs10(
1588
+ "li",
1589
+ {
1590
+ className: cn(
1591
+ "flex items-center justify-between gap-2 rounded-lg border px-3 py-2 dark:border-gray-700",
1592
+ classNames?.item
1593
+ ),
1594
+ children: [
1595
+ /* @__PURE__ */ jsxs10("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
1596
+ canArchive ? /* @__PURE__ */ jsx15(
1597
+ "input",
1598
+ {
1599
+ type: "checkbox",
1600
+ checked: selectedIds.includes(document2.id),
1601
+ onChange: () => toggleSelected(document2.id),
1602
+ "aria-label": `${labels.download}: ${document2.filename}`,
1603
+ className: cn("shrink-0", classNames?.checkbox)
1604
+ }
1605
+ ) : null,
1606
+ /* @__PURE__ */ jsx15(FileIcon, { filename: document2.filename, mimeType: document2.mimeType }),
1607
+ /* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1", children: [
1608
+ /* @__PURE__ */ jsxs10(
1609
+ "div",
1610
+ {
1611
+ className: cn(
1612
+ "mb-0.5 flex items-center gap-1 text-[11px] font-medium",
1613
+ isFromCustomer ? "text-blue-600 dark:text-blue-400" : "text-emerald-600 dark:text-emerald-400",
1614
+ classNames?.sourceBadge
1615
+ ),
1616
+ children: [
1617
+ /* @__PURE__ */ jsx15(SourceIcon, { size: 11 }),
1618
+ isFromCustomer ? labels.sourceFilterCustomer : labels.sourceFilterTeam
1619
+ ]
1620
+ }
1621
+ ),
1622
+ /* @__PURE__ */ jsx15(
1623
+ "div",
1624
+ {
1625
+ className: cn("truncate text-sm font-medium", classNames?.filename),
1626
+ title: document2.filename,
1627
+ children: document2.filename
1628
+ }
1629
+ ),
1630
+ /* @__PURE__ */ jsxs10("div", { className: cn("text-xs text-gray-500 dark:text-gray-400", classNames?.meta), children: [
1631
+ formatDateTime(document2.linkedAt),
1632
+ " \xB7 ",
1633
+ formatFileSize(document2.sizeBytes)
1634
+ ] })
1635
+ ] })
1636
+ ] }),
1637
+ /* @__PURE__ */ jsxs10("div", { className: "flex shrink-0 gap-1", children: [
1638
+ /* @__PURE__ */ jsx15(
1639
+ "button",
1640
+ {
1641
+ type: "button",
1642
+ onClick: () => void handleOpen(document2.id, "inline"),
1643
+ title: labels.view,
1644
+ "aria-label": `${labels.view}: ${document2.filename}`,
1645
+ className: cn("cv-header-icon", classNames?.viewButton),
1646
+ children: /* @__PURE__ */ jsx15(Eye, { size: 14 })
1647
+ }
1648
+ ),
1649
+ /* @__PURE__ */ jsx15(
1650
+ "button",
1651
+ {
1652
+ type: "button",
1653
+ onClick: () => void handleOpen(document2.id, "attachment"),
1654
+ title: labels.download,
1655
+ "aria-label": `${labels.download}: ${document2.filename}`,
1656
+ className: cn("cv-header-icon", classNames?.downloadButton),
1657
+ children: /* @__PURE__ */ jsx15(Download, { size: 14 })
1658
+ }
1659
+ )
1660
+ ] })
1661
+ ]
1662
+ },
1663
+ document2.id
1664
+ );
1665
+ }) }),
1666
+ total > perPage ? /* @__PURE__ */ jsxs10(
1667
+ "div",
1668
+ {
1669
+ className: cn(
1670
+ "mt-2 flex items-center justify-between border-t pt-2 text-xs dark:border-gray-700",
1671
+ classNames?.pagination
1672
+ ),
1673
+ children: [
1674
+ /* @__PURE__ */ jsx15("span", { className: "text-gray-400", children: labels.total(total) }),
1675
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
1676
+ /* @__PURE__ */ jsx15(
1677
+ "button",
1678
+ {
1679
+ type: "button",
1680
+ onClick: () => setPage(page - 1),
1681
+ disabled: page <= 1,
1682
+ "aria-label": labels.sortOldest,
1683
+ className: "cv-header-icon disabled:opacity-40",
1684
+ children: "\u2039"
1685
+ }
1686
+ ),
1687
+ /* @__PURE__ */ jsx15("span", { className: "text-gray-500", children: labels.page(page, lastPage) }),
1688
+ /* @__PURE__ */ jsx15(
1689
+ "button",
1690
+ {
1691
+ type: "button",
1692
+ onClick: () => setPage(page + 1),
1693
+ disabled: page >= lastPage,
1694
+ "aria-label": labels.sortMostRecent,
1695
+ className: "cv-header-icon disabled:opacity-40",
1696
+ children: "\u203A"
1697
+ }
1698
+ )
1699
+ ] })
1700
+ ]
1701
+ }
1702
+ ) : null
1703
+ ] }) });
1704
+ }
1705
+
1706
+ // src/DocumentsLibrary.tsx
1707
+ import { useEffect as useEffect4, useState as useState10 } from "react";
1708
+ import { ArrowUpDown as ArrowUpDown2, Bot as Bot2, Download as Download2, Eye as Eye2, MessageSquare, Users as Users2 } from "lucide-react";
1709
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
1710
+ var DEFAULT_DOCUMENTS_LIBRARY_LABELS = {
1711
+ title: "Documentos",
1712
+ searchPlaceholder: "Buscar por nome do arquivo ou telefone",
1713
+ empty: "Nenhum arquivo trocado ainda.",
1714
+ noResults: "Nenhum arquivo encontrado para os filtros aplicados",
1715
+ loading: "Carregando arquivos\u2026",
1716
+ failure: "N\xE3o foi poss\xEDvel carregar os arquivos.",
1717
+ view: "Visualizar",
1718
+ download: "Baixar",
1719
+ openConversation: "Abrir conversa",
1720
+ sourceFilterAll: "Todas as origens",
1721
+ sourceFilterCustomer: "Cliente",
1722
+ sourceFilterTeam: "Equipe",
1723
+ sortMostRecent: "Mais recentes",
1724
+ sortOldest: "Mais antigos",
1725
+ clearFilters: "Limpar filtros",
1726
+ total: (count) => `${count} arquivo${count === 1 ? "" : "s"}`,
1727
+ page: (current, last) => `${current} / ${last}`
1728
+ };
1729
+ var TEAM_SOURCES2 = /* @__PURE__ */ new Set(["agent", "bot"]);
1730
+ var DEFAULT_PER_PAGE2 = 20;
1731
+ function DocumentsLibrary({
1732
+ perPage = DEFAULT_PER_PAGE2,
1733
+ onOpenConversation,
1734
+ labels: labelsOverride,
1735
+ className,
1736
+ classNames
1737
+ }) {
1738
+ const labels = { ...DEFAULT_DOCUMENTS_LIBRARY_LABELS, ...labelsOverride };
1739
+ const context = useConversations();
1740
+ const [search, setSearch] = useState10("");
1741
+ const [sourceFilter, setSourceFilter] = useState10(DOCUMENT_SOURCE_FILTER.ALL);
1742
+ const [sortDirection, setSortDirection] = useState10("desc");
1743
+ const [page, setPage] = useState10(1);
1744
+ const [documents, setDocuments] = useState10([]);
1745
+ const [total, setTotal] = useState10(0);
1746
+ const [loading, setLoading] = useState10(false);
1747
+ const [failed, setFailed] = useState10(false);
1748
+ const hasFilters = search !== "" || sourceFilter !== DOCUMENT_SOURCE_FILTER.ALL || sortDirection !== "desc";
1749
+ const lastPage = Math.max(1, Math.ceil(total / perPage));
1750
+ const fetchAll = context?.api.getAllDocuments;
1751
+ useEffect4(() => {
1752
+ if (!fetchAll) return;
1753
+ let active = true;
1754
+ setLoading(true);
1755
+ setFailed(false);
1756
+ void fetchAll({
1757
+ search,
1758
+ page,
1759
+ limit: perPage,
1760
+ sortDirection,
1761
+ ...sourceFilter === DOCUMENT_SOURCE_FILTER.ALL ? {} : { source: sourceFilter }
1762
+ }).then((result) => {
1763
+ if (!active) return;
1764
+ setDocuments(result.documents);
1765
+ setTotal(result.total);
1766
+ }).catch(() => {
1767
+ if (active) setFailed(true);
1768
+ }).finally(() => {
1769
+ if (active) setLoading(false);
1770
+ });
1771
+ return () => {
1772
+ active = false;
1773
+ };
1774
+ }, [fetchAll, search, sourceFilter, sortDirection, page, perPage]);
1775
+ function applyFilter(change) {
1776
+ change();
1777
+ setPage(1);
1778
+ }
1779
+ async function handleOpen(uploadId, disposition) {
1780
+ const url = await context?.api.getDocumentUrl(uploadId, disposition);
1781
+ if (url) window.open(url, "_blank", "noopener,noreferrer");
1782
+ }
1783
+ if (!fetchAll) return null;
1784
+ return /* @__PURE__ */ jsxs11("div", { className: cn("space-y-3", classNames?.root, className), children: [
1785
+ /* @__PURE__ */ jsx16("h2", { className: cn("text-lg font-semibold", classNames?.title), children: labels.title }),
1786
+ /* @__PURE__ */ jsxs11("div", { className: cn("flex flex-wrap items-center gap-2", classNames?.filters), children: [
1787
+ /* @__PURE__ */ jsx16(
1788
+ "input",
1789
+ {
1790
+ type: "search",
1791
+ value: search,
1792
+ onChange: (event) => applyFilter(() => setSearch(event.target.value)),
1793
+ placeholder: labels.searchPlaceholder,
1794
+ "aria-label": labels.searchPlaceholder,
1795
+ className: cn("w-full rounded-md border px-3 py-2 text-sm sm:w-64", classNames?.search)
1796
+ }
1797
+ ),
1798
+ /* @__PURE__ */ jsxs11(
1799
+ "select",
1800
+ {
1801
+ value: sourceFilter,
1802
+ onChange: (event) => applyFilter(() => setSourceFilter(event.target.value)),
1803
+ "aria-label": labels.sourceFilterAll,
1804
+ className: cn("w-full rounded-md border px-2 py-2 text-sm sm:w-40", classNames?.sourceSelect),
1805
+ children: [
1806
+ /* @__PURE__ */ jsx16("option", { value: DOCUMENT_SOURCE_FILTER.ALL, children: labels.sourceFilterAll }),
1807
+ /* @__PURE__ */ jsx16("option", { value: DOCUMENT_SOURCE_FILTER.CUSTOMER, children: labels.sourceFilterCustomer }),
1808
+ /* @__PURE__ */ jsx16("option", { value: DOCUMENT_SOURCE_FILTER.TEAM, children: labels.sourceFilterTeam })
1809
+ ]
1810
+ }
1811
+ ),
1812
+ /* @__PURE__ */ jsxs11(
1813
+ "button",
1814
+ {
1815
+ type: "button",
1816
+ onClick: () => applyFilter(() => setSortDirection(sortDirection === "desc" ? "asc" : "desc")),
1817
+ className: cn("cv-header-action inline-flex items-center gap-1", classNames?.sortButton),
1818
+ children: [
1819
+ /* @__PURE__ */ jsx16(ArrowUpDown2, { size: 14 }),
1820
+ sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest
1821
+ ]
1822
+ }
1823
+ ),
1824
+ hasFilters ? /* @__PURE__ */ jsx16(
1825
+ "button",
1826
+ {
1827
+ type: "button",
1828
+ onClick: () => applyFilter(() => {
1829
+ setSearch("");
1830
+ setSourceFilter(DOCUMENT_SOURCE_FILTER.ALL);
1831
+ setSortDirection("desc");
1832
+ }),
1833
+ className: cn("cv-header-action", classNames?.clearButton),
1834
+ children: labels.clearFilters
1835
+ }
1836
+ ) : null
1837
+ ] }),
1838
+ loading ? /* @__PURE__ */ jsx16("p", { className: cn("text-sm text-gray-500", classNames?.status), children: labels.loading }) : null,
1839
+ failed ? /* @__PURE__ */ jsx16("p", { role: "alert", className: cn("text-sm text-red-600 dark:text-red-400", classNames?.status), children: labels.failure }) : null,
1840
+ !loading && !failed && documents.length === 0 ? /* @__PURE__ */ jsx16("p", { className: cn("text-sm text-gray-500", classNames?.status), children: hasFilters ? labels.noResults : labels.empty }) : null,
1841
+ /* @__PURE__ */ jsx16("ul", { className: cn("space-y-2", classNames?.list), children: documents.map((document2) => {
1842
+ const isFromCustomer = !TEAM_SOURCES2.has(document2.source);
1843
+ const SourceIcon = isFromCustomer ? Users2 : Bot2;
1844
+ return /* @__PURE__ */ jsxs11(
1845
+ "li",
1846
+ {
1847
+ className: cn(
1848
+ "flex items-center justify-between gap-3 rounded-lg border px-3 py-2 dark:border-gray-700",
1849
+ classNames?.item
1850
+ ),
1851
+ children: [
1852
+ /* @__PURE__ */ jsxs11("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
1853
+ /* @__PURE__ */ jsx16(FileIcon, { filename: document2.filename, mimeType: document2.mimeType }),
1854
+ /* @__PURE__ */ jsxs11("div", { className: "min-w-0 flex-1", children: [
1855
+ /* @__PURE__ */ jsx16("div", { className: cn("truncate text-sm font-medium", classNames?.filename), title: document2.filename, children: document2.filename }),
1856
+ /* @__PURE__ */ jsxs11("div", { className: cn("flex flex-wrap items-center gap-x-2 text-xs text-gray-500", classNames?.meta), children: [
1857
+ /* @__PURE__ */ jsxs11("span", { className: "inline-flex items-center gap-1", children: [
1858
+ /* @__PURE__ */ jsx16(SourceIcon, { size: 11 }),
1859
+ isFromCustomer ? labels.sourceFilterCustomer : labels.sourceFilterTeam
1860
+ ] }),
1861
+ /* @__PURE__ */ jsx16("span", { children: "\xB7" }),
1862
+ /* @__PURE__ */ jsx16("span", { children: formatDateTime(document2.linkedAt) }),
1863
+ /* @__PURE__ */ jsx16("span", { children: "\xB7" }),
1864
+ /* @__PURE__ */ jsx16("span", { children: formatFileSize(document2.sizeBytes) })
1865
+ ] })
1866
+ ] })
1867
+ ] }),
1868
+ onOpenConversation ? /* @__PURE__ */ jsxs11(
1869
+ "button",
1870
+ {
1871
+ type: "button",
1872
+ onClick: () => onOpenConversation(document2.conversationId),
1873
+ title: labels.openConversation,
1874
+ className: cn("cv-header-action inline-flex shrink-0 items-center gap-1", classNames?.conversationLink),
1875
+ children: [
1876
+ /* @__PURE__ */ jsx16(MessageSquare, { size: 12 }),
1877
+ formatPhone(document2.conversationId)
1878
+ ]
1879
+ }
1880
+ ) : /* @__PURE__ */ jsx16("span", { className: cn("shrink-0 text-xs text-gray-500", classNames?.conversationLink), children: formatPhone(document2.conversationId) }),
1881
+ /* @__PURE__ */ jsxs11("div", { className: "flex shrink-0 gap-1", children: [
1882
+ /* @__PURE__ */ jsx16(
1883
+ "button",
1884
+ {
1885
+ type: "button",
1886
+ onClick: () => void handleOpen(document2.id, "inline"),
1887
+ title: labels.view,
1888
+ "aria-label": `${labels.view}: ${document2.filename}`,
1889
+ className: "cv-header-icon",
1890
+ children: /* @__PURE__ */ jsx16(Eye2, { size: 14 })
1891
+ }
1892
+ ),
1893
+ /* @__PURE__ */ jsx16(
1894
+ "button",
1895
+ {
1896
+ type: "button",
1897
+ onClick: () => void handleOpen(document2.id, "attachment"),
1898
+ title: labels.download,
1899
+ "aria-label": `${labels.download}: ${document2.filename}`,
1900
+ className: "cv-header-icon",
1901
+ children: /* @__PURE__ */ jsx16(Download2, { size: 14 })
1902
+ }
1903
+ )
1904
+ ] })
1905
+ ]
1906
+ },
1907
+ `${document2.conversationId}:${document2.id}`
1908
+ );
1909
+ }) }),
1910
+ total > perPage ? /* @__PURE__ */ jsxs11("div", { className: cn("flex items-center justify-between border-t pt-2 text-xs dark:border-gray-700", classNames?.pagination), children: [
1911
+ /* @__PURE__ */ jsx16("span", { className: "text-gray-400", children: labels.total(total) }),
1912
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
1913
+ /* @__PURE__ */ jsx16(
1914
+ "button",
1915
+ {
1916
+ type: "button",
1917
+ onClick: () => setPage(page - 1),
1918
+ disabled: page <= 1,
1919
+ className: "cv-header-icon disabled:opacity-40",
1920
+ children: "\u2039"
1921
+ }
1922
+ ),
1923
+ /* @__PURE__ */ jsx16("span", { className: "text-gray-500", children: labels.page(page, lastPage) }),
1924
+ /* @__PURE__ */ jsx16(
1925
+ "button",
1926
+ {
1927
+ type: "button",
1928
+ onClick: () => setPage(page + 1),
1929
+ disabled: page >= lastPage,
1930
+ className: "cv-header-icon disabled:opacity-40",
1931
+ children: "\u203A"
1932
+ }
1933
+ )
1934
+ ] })
1935
+ ] }) : null
1936
+ ] });
1937
+ }
1938
+
1939
+ export {
1940
+ ConversationLocalesProvider,
1941
+ useConversationLocales,
1942
+ StatusTicks,
1943
+ AudioPlayer,
1944
+ cn,
1945
+ FileIcon,
1946
+ formatTimestamp,
1947
+ formatDateTime,
1948
+ isSameDay,
1949
+ formatFileSize,
1950
+ MediaRenderer,
1951
+ DEFAULT_LIGHTBOX_LABELS,
1952
+ Lightbox,
1953
+ DEFAULT_INTERACTIVE_MESSAGE_LABELS,
1954
+ InteractiveMessage,
1955
+ createMediaUrlResolver,
1956
+ ConversationsProvider,
1957
+ useConversations,
1958
+ MessageBubble,
1959
+ ConversationWallpaper,
1960
+ EMOJI_CATEGORIES,
1961
+ searchEmojis,
1962
+ DEFAULT_EMOJI_PICKER_LABELS,
1963
+ EmojiPicker,
1964
+ applyQuickReplyVariables,
1965
+ resolveQuickReply,
1966
+ DEFAULT_MESSAGE_COMPOSER_LABELS,
1967
+ DEFAULT_ACCEPTED_FILE_TYPES,
1968
+ MessageComposer,
1969
+ DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
1970
+ DEFAULT_MAX_RECORDING_MILLISECONDS,
1971
+ AudioRecorderButton,
1972
+ DateDivider,
1973
+ formatPhone,
1974
+ phoneCountryFlag,
1975
+ phoneInitials,
1976
+ useAsyncResource,
1977
+ conversationsOf,
1978
+ totalOf,
1979
+ useConversationDocuments,
1980
+ DOCUMENT_SOURCE_FILTER,
1981
+ DEFAULT_CONVERSATION_DOCUMENTS_LABELS,
1982
+ ConversationDocumentsPanel,
1983
+ DEFAULT_DOCUMENTS_LIBRARY_LABELS,
1984
+ DocumentsLibrary
1985
+ };