@adatechnology/conversations-ui 0.1.0-rc.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ConversationSimulatorPanel--5fIzXWY.d.ts +804 -0
- package/dist/{chunk-G5BM3VBP.js → chunk-BJNRLLDO.js} +1248 -282
- package/dist/chunk-DKPXKQGC.js +110 -0
- package/dist/{chunk-2AYDBWNE.js → chunk-WCBDXZ3X.js} +13 -3
- package/dist/flows/index.d.ts +422 -5
- package/dist/flows/index.js +2502 -676
- package/dist/index.d.ts +919 -17
- package/dist/index.js +3676 -675
- package/dist/preview/index.d.ts +62 -105
- package/dist/preview/index.js +162 -284
- package/dist/styles.css +893 -0
- package/package.json +9 -8
- package/src/AudioPlayer.tsx +8 -0
- package/src/AudioRecorderButton.test.tsx +30 -0
- package/src/AudioRecorderButton.tsx +248 -0
- package/src/AudioTranscription.test.tsx +115 -0
- package/src/AudioTranscription.tsx +252 -0
- package/src/Avatar.tsx +1 -1
- package/src/ConversationContextPanel.tsx +218 -44
- package/src/ConversationDocumentsPanel.tsx +11 -6
- package/src/ConversationHeader.test.tsx +66 -0
- package/src/ConversationHeader.tsx +147 -47
- package/src/ConversationListItem.tsx +8 -6
- package/src/ConversationLocalesProvider.tsx +28 -0
- package/src/ConversationRow.tsx +53 -7
- package/src/DarkModeToggle.test.tsx +76 -0
- package/src/DarkModeToggle.tsx +92 -0
- package/src/DocumentsLibrary.tsx +67 -7
- package/src/EmojiPicker.tsx +2 -1
- package/src/InteractiveMessage.tsx +3 -0
- package/src/Lightbox.tsx +1 -1
- package/src/MediaRenderer.tsx +88 -15
- package/src/MessageBubble.test.tsx +41 -0
- package/src/MessageBubble.tsx +47 -5
- package/src/MessageComposer.test.tsx +35 -0
- package/src/MessageComposer.tsx +122 -17
- package/src/MessageText.tsx +2 -1
- package/src/MessageTimestamp.tsx +2 -1
- package/src/RichMessageComposer.test.tsx +113 -0
- package/src/RichMessageComposer.tsx +551 -0
- package/src/SimpleEmojiPicker.tsx +5 -3
- package/src/StatusTicks.tsx +1 -1
- package/src/Toast.tsx +4 -0
- package/src/Tooltip.test.ts +42 -0
- package/src/Tooltip.tsx +167 -0
- package/src/Wallpaper.tsx +27 -13
- package/src/WhatsAppMessageEditor.tsx +10 -7
- package/src/WindowExpiredNotice.tsx +12 -4
- package/src/{preview/audioRecorderFormat.test.ts → audioRecorderFormat.test.ts} +1 -1
- package/src/buildOutput.test.ts +79 -0
- package/src/composer.constant.ts +33 -0
- package/src/conversationTranscript.test.ts +57 -0
- package/src/conversationTranscript.ts +29 -4
- package/src/conversationWindow.ts +7 -5
- package/src/documentTypeLabel.test.ts +57 -0
- package/src/documents/DocumentsWorkspace.tsx +550 -0
- package/src/documents/index.ts +8 -0
- package/src/documents/labels.ts +92 -0
- package/src/flows/FlowConnectionEdge.tsx +104 -0
- package/src/flows/FlowGroupHeader.tsx +12 -2
- package/src/flows/FlowLegend.tsx +125 -0
- package/src/flows/FlowMapCanvas.tsx +15 -12
- package/src/flows/FlowMapNode.tsx +4 -1
- package/src/flows/FlowNodeCard.tsx +219 -34
- package/src/flows/FlowNodePanel.tsx +153 -39
- package/src/flows/FlowPalette.tsx +156 -70
- package/src/flows/FlowPortalNode.tsx +1 -1
- package/src/flows/FlowWhatsAppPreview.tsx +14 -3
- package/src/flows/FlowsWorkspace.tsx +1255 -0
- package/src/flows/flowCanvasModel.test.ts +456 -0
- package/src/flows/flowCanvasModel.ts +378 -0
- package/src/flows/flowEditorOps.test.ts +276 -0
- package/src/flows/flowEditorOps.ts +202 -0
- package/src/flows/flowGraph.ts +78 -53
- package/src/flows/flowMenuPlacement.test.ts +130 -0
- package/src/flows/flowMenuPlacement.ts +86 -0
- package/src/flows/index.ts +51 -2
- package/src/flows/labels.ts +180 -0
- package/src/flows/workspaceContract.test.ts +126 -0
- package/src/hooks/useContainerWidth.ts +35 -0
- package/src/hooks/useConversationRealtime.ts +10 -8
- package/src/hooks/useScrollToLatestMessage.ts +127 -0
- package/src/hooks/useUrlFilterState.ts +107 -0
- package/src/icon.constant.ts +12 -0
- package/src/index.ts +100 -0
- package/src/lib/composer-formatting.test.ts +78 -0
- package/src/lib/composer-formatting.ts +145 -0
- package/src/lib/whatsapp-formatting.test.tsx +37 -0
- package/src/lib/whatsapp-formatting.tsx +28 -3
- package/src/listing/index.tsx +202 -0
- package/src/pagination.constant.ts +10 -0
- package/src/preview/ConversationPreview.tsx +84 -45
- package/src/preview/ConversationSimulatorClient.ts +143 -0
- package/src/preview/ConversationSimulatorPanel.test.tsx +55 -0
- package/src/preview/ConversationSimulatorPanel.tsx +131 -0
- package/src/preview/createPreviewBridgeClient.test.ts +92 -0
- package/src/preview/createPreviewBridgeClient.ts +124 -0
- package/src/preview/createPreviewMediaUploader.ts +82 -0
- package/src/preview/createPreviewWebhookClient.test.ts +96 -0
- package/src/preview/createPreviewWebhookClient.ts +99 -3
- package/src/preview/index.ts +36 -2
- package/src/preview/previewMediaUploader.test.ts +61 -0
- package/src/providers/ConversationsProvider.tsx +8 -6
- package/src/providers/types.ts +59 -2
- package/src/quickReply.test.ts +58 -0
- package/src/replyLatency.test.ts +71 -0
- package/src/replyLatency.ts +57 -0
- package/src/settings/MessagesWorkspace.tsx +571 -0
- package/src/settings/TopicsForm.tsx +2 -0
- package/src/settings/TranscriptionSettingsForm.test.tsx +81 -0
- package/src/settings/TranscriptionSettingsForm.tsx +190 -0
- package/src/settings/WelcomeFarewellForm.tsx +1 -0
- package/src/settings/WhatsAppCreateTemplateForm.tsx +1 -0
- package/src/settings/WhatsAppTemplateSettingsForm.tsx +5 -2
- package/src/settings/WhatsAppTemplatesSettings.test.tsx +61 -0
- package/src/settings/WhatsAppTemplatesSettings.tsx +22 -2
- package/src/styles.css +858 -0
- package/src/theme.ts +13 -0
- package/src/types.ts +26 -0
- package/src/workspace/BulkTemplateModal.tsx +132 -0
- package/src/workspace/ConversationPane.tsx +432 -0
- package/src/workspace/ConversationsInboxList.tsx +194 -0
- package/src/workspace/ConversationsWorkspace.tsx +423 -0
- package/src/workspace/index.ts +17 -0
- package/src/workspace/labels.test.ts +17 -0
- package/src/workspace/labels.ts +85 -0
- package/src/workspace/useConversationsInbox.ts +332 -0
- package/dist/types-B5C1DLu1.d.ts +0 -365
- package/src/preview/AudioRecorderButton.tsx +0 -117
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseWhatsAppFormatting,
|
|
3
3
|
useIsDarkTheme
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WCBDXZ3X.js";
|
|
5
5
|
|
|
6
6
|
// src/ConversationLocalesProvider.tsx
|
|
7
7
|
import { createContext, useContext } from "react";
|
|
@@ -28,6 +28,19 @@ var DEFAULT_LOCALES = {
|
|
|
28
28
|
untitledDocument: "Documento",
|
|
29
29
|
downloadFile: "Baixar"
|
|
30
30
|
},
|
|
31
|
+
transcription: {
|
|
32
|
+
label: "Transcri\xE7\xE3o",
|
|
33
|
+
copy: "Copiar",
|
|
34
|
+
copied: "Copiado!",
|
|
35
|
+
transcribe: "Transcrever \xE1udio",
|
|
36
|
+
transcribing: "Transcrevendo...",
|
|
37
|
+
retry: "Transcrever novamente",
|
|
38
|
+
failed: "Falha ao transcrever \u2014 tentar novamente",
|
|
39
|
+
empty: "Sem fala detectada",
|
|
40
|
+
unsupported: "Formato de \xE1udio n\xE3o suportado para transcri\xE7\xE3o",
|
|
41
|
+
showMore: "ver transcri\xE7\xE3o completa",
|
|
42
|
+
showLess: "ver menos"
|
|
43
|
+
},
|
|
31
44
|
selection: {
|
|
32
45
|
select: "Selecionar"
|
|
33
46
|
},
|
|
@@ -40,6 +53,7 @@ var ConversationLocalesContext = createContext(DEFAULT_LOCALES);
|
|
|
40
53
|
function ConversationLocalesProvider({ children, locales }) {
|
|
41
54
|
const merged = {
|
|
42
55
|
bubble: { ...DEFAULT_LOCALES.bubble, ...locales?.bubble },
|
|
56
|
+
transcription: { ...DEFAULT_LOCALES.transcription, ...locales?.transcription },
|
|
43
57
|
selection: { ...DEFAULT_LOCALES.selection, ...locales?.selection },
|
|
44
58
|
dateDivider: { ...DEFAULT_LOCALES.dateDivider, ...locales?.dateDivider }
|
|
45
59
|
};
|
|
@@ -75,7 +89,7 @@ function Ticks({ double }) {
|
|
|
75
89
|
}
|
|
76
90
|
function StatusTicks({ status, title }) {
|
|
77
91
|
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" }) });
|
|
92
|
+
return /* @__PURE__ */ jsx2("span", { className: `cursor-help leading-none flex items-center ${colorClass}`, "data-cv-tooltip": title, children: status === "failed" ? /* @__PURE__ */ jsx2(AlertTriangle, { size: 11 }) : /* @__PURE__ */ jsx2(Ticks, { double: status !== "sent" }) });
|
|
79
93
|
}
|
|
80
94
|
|
|
81
95
|
// src/AudioPlayer.tsx
|
|
@@ -86,6 +100,10 @@ var BARS = Array.from({ length: 30 }, (_, i) => {
|
|
|
86
100
|
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
101
|
return heights[i % heights.length];
|
|
88
102
|
});
|
|
103
|
+
var AUDIO_PLAYER_LABELS = {
|
|
104
|
+
play: "Reproduzir",
|
|
105
|
+
pause: "Pausar"
|
|
106
|
+
};
|
|
89
107
|
function fmt(sec) {
|
|
90
108
|
if (!isFinite(sec)) return "0:00";
|
|
91
109
|
const m = Math.floor(sec / 60);
|
|
@@ -147,6 +165,8 @@ function AudioPlayer({ src, isMine = false }) {
|
|
|
147
165
|
/* @__PURE__ */ jsx3(
|
|
148
166
|
"button",
|
|
149
167
|
{
|
|
168
|
+
"data-cv-tooltip": playing ? AUDIO_PLAYER_LABELS.pause : AUDIO_PLAYER_LABELS.play,
|
|
169
|
+
"aria-label": playing ? AUDIO_PLAYER_LABELS.pause : AUDIO_PLAYER_LABELS.play,
|
|
150
170
|
onClick: toggle,
|
|
151
171
|
className: `flex-shrink-0 w-9 h-9 rounded-full flex items-center justify-center transition-colors ${playBtn}`,
|
|
152
172
|
children: playing ? /* @__PURE__ */ jsx3(Pause, { size: 16 }) : /* @__PURE__ */ jsx3(Play, { size: 16, className: "translate-x-0.5" })
|
|
@@ -166,36 +186,189 @@ function AudioPlayer({ src, isMine = false }) {
|
|
|
166
186
|
] });
|
|
167
187
|
}
|
|
168
188
|
|
|
189
|
+
// src/AudioTranscription.tsx
|
|
190
|
+
import { useCallback, useState as useState2 } from "react";
|
|
191
|
+
import { Check, Copy, FileText, Loader2, RefreshCw } from "lucide-react";
|
|
192
|
+
|
|
193
|
+
// src/lib/cn.ts
|
|
194
|
+
import { clsx } from "clsx";
|
|
195
|
+
import { twMerge } from "tailwind-merge";
|
|
196
|
+
function cn(...inputs) {
|
|
197
|
+
return twMerge(clsx(inputs));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/AudioTranscription.tsx
|
|
201
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
202
|
+
var COPIED_FEEDBACK_MS = 2e3;
|
|
203
|
+
var COLLAPSE_ABOVE_CHARS = 320;
|
|
204
|
+
var COLLAPSED_LINE_CLAMP = 4;
|
|
205
|
+
function AudioTranscription({ transcription, onTranscribe, isMine = false }) {
|
|
206
|
+
const { transcription: locales } = useConversationLocales();
|
|
207
|
+
const [hasCopied, setHasCopied] = useState2(false);
|
|
208
|
+
const [isTranscribing, setIsTranscribing] = useState2(false);
|
|
209
|
+
const [hasRequestFailed, setHasRequestFailed] = useState2(false);
|
|
210
|
+
const [justTranscribed, setJustTranscribed] = useState2();
|
|
211
|
+
const [isExpanded, setIsExpanded] = useState2(false);
|
|
212
|
+
const effective = justTranscribed ?? transcription;
|
|
213
|
+
const text = effective?.text?.trim() ?? "";
|
|
214
|
+
const status = effective?.status;
|
|
215
|
+
const isLong = text.length > COLLAPSE_ABOVE_CHARS;
|
|
216
|
+
const isTruncated = isLong && !isExpanded;
|
|
217
|
+
const isDone = status === "done";
|
|
218
|
+
const hasText = isDone && text.length > 0;
|
|
219
|
+
const isSilent = isDone && text.length === 0;
|
|
220
|
+
const handleCopy = useCallback(async () => {
|
|
221
|
+
if (!text) return;
|
|
222
|
+
try {
|
|
223
|
+
await navigator.clipboard.writeText(text);
|
|
224
|
+
setHasCopied(true);
|
|
225
|
+
setTimeout(() => setHasCopied(false), COPIED_FEEDBACK_MS);
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
}, [text]);
|
|
229
|
+
const handleTranscribe = useCallback(async () => {
|
|
230
|
+
if (!onTranscribe || isTranscribing) return;
|
|
231
|
+
setIsTranscribing(true);
|
|
232
|
+
setHasRequestFailed(false);
|
|
233
|
+
try {
|
|
234
|
+
const result = await onTranscribe();
|
|
235
|
+
if (result) setJustTranscribed(result);
|
|
236
|
+
} catch {
|
|
237
|
+
setHasRequestFailed(true);
|
|
238
|
+
} finally {
|
|
239
|
+
setIsTranscribing(false);
|
|
240
|
+
}
|
|
241
|
+
}, [onTranscribe, isTranscribing]);
|
|
242
|
+
const dividerClass = isMine ? "border-black/10 dark:border-white/10" : "border-black/10 dark:border-white/10";
|
|
243
|
+
if (!status && !onTranscribe) return null;
|
|
244
|
+
if (!status || status === "pending" || status === "failed") {
|
|
245
|
+
return /* @__PURE__ */ jsx4("div", { className: `mt-1.5 border-t pt-1.5 ${dividerClass}`, children: /* @__PURE__ */ jsx4(
|
|
246
|
+
TranscribeButton,
|
|
247
|
+
{
|
|
248
|
+
label: resolveActionLabel({ status, hasRequestFailed, isTranscribing, locales }),
|
|
249
|
+
isBusy: isTranscribing,
|
|
250
|
+
onClick: handleTranscribe,
|
|
251
|
+
isDisabled: !onTranscribe
|
|
252
|
+
}
|
|
253
|
+
) });
|
|
254
|
+
}
|
|
255
|
+
if (status === "unsupported") {
|
|
256
|
+
return /* @__PURE__ */ jsx4("div", { className: `mt-1.5 border-t pt-1.5 ${dividerClass}`, children: /* @__PURE__ */ jsx4("p", { className: "text-xs italic text-gray-500 dark:text-gray-400", children: locales.unsupported }) });
|
|
257
|
+
}
|
|
258
|
+
return /* @__PURE__ */ jsxs3("div", { className: `mt-1.5 border-t pt-1.5 ${dividerClass}`, children: [
|
|
259
|
+
/* @__PURE__ */ jsxs3("div", { className: "mb-0.5 flex items-center gap-1.5", children: [
|
|
260
|
+
/* @__PURE__ */ jsx4(FileText, { size: 11, className: "flex-shrink-0 text-gray-500 dark:text-gray-400", "aria-hidden": true }),
|
|
261
|
+
/* @__PURE__ */ jsx4("span", { className: "text-[11px] font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400", children: locales.label }),
|
|
262
|
+
hasText && /* @__PURE__ */ jsx4(
|
|
263
|
+
"button",
|
|
264
|
+
{
|
|
265
|
+
onClick: handleCopy,
|
|
266
|
+
"data-cv-tooltip": locales.copy,
|
|
267
|
+
"aria-label": hasCopied ? locales.copied : locales.copy,
|
|
268
|
+
className: "ml-auto flex flex-shrink-0 items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-gray-500 transition-colors hover:bg-black/5 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-gray-200",
|
|
269
|
+
children: hasCopied ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
270
|
+
/* @__PURE__ */ jsx4(Check, { size: 11, "aria-hidden": true }),
|
|
271
|
+
/* @__PURE__ */ jsx4("span", { children: locales.copied })
|
|
272
|
+
] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
273
|
+
/* @__PURE__ */ jsx4(Copy, { size: 11, "aria-hidden": true }),
|
|
274
|
+
/* @__PURE__ */ jsx4("span", { children: locales.copy })
|
|
275
|
+
] })
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
] }),
|
|
279
|
+
isSilent ? /* @__PURE__ */ jsx4("p", { className: "text-xs italic text-gray-500 dark:text-gray-400", children: locales.empty }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
280
|
+
/* @__PURE__ */ jsx4(
|
|
281
|
+
"p",
|
|
282
|
+
{
|
|
283
|
+
className: cn(
|
|
284
|
+
"select-all whitespace-pre-wrap break-words text-[13px] leading-[18px] text-gray-700 dark:text-gray-200",
|
|
285
|
+
isTruncated && "overflow-hidden"
|
|
286
|
+
),
|
|
287
|
+
style: isTruncated ? {
|
|
288
|
+
display: "-webkit-box",
|
|
289
|
+
WebkitBoxOrient: "vertical",
|
|
290
|
+
WebkitLineClamp: COLLAPSED_LINE_CLAMP
|
|
291
|
+
} : void 0,
|
|
292
|
+
children: text
|
|
293
|
+
}
|
|
294
|
+
),
|
|
295
|
+
isLong && /* @__PURE__ */ jsx4(
|
|
296
|
+
"button",
|
|
297
|
+
{
|
|
298
|
+
"data-cv-tooltip": isExpanded ? locales.showLess : locales.showMore,
|
|
299
|
+
"aria-label": isExpanded ? locales.showLess : locales.showMore,
|
|
300
|
+
onClick: () => setIsExpanded((current) => !current),
|
|
301
|
+
className: "mt-1 text-[11px] font-medium text-gray-500 underline decoration-dotted hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200",
|
|
302
|
+
children: isExpanded ? locales.showLess : locales.showMore
|
|
303
|
+
}
|
|
304
|
+
)
|
|
305
|
+
] }),
|
|
306
|
+
onTranscribe && /* @__PURE__ */ jsxs3(
|
|
307
|
+
"button",
|
|
308
|
+
{
|
|
309
|
+
"data-cv-tooltip": isTranscribing ? locales.transcribing : locales.retry,
|
|
310
|
+
"aria-label": isTranscribing ? locales.transcribing : locales.retry,
|
|
311
|
+
onClick: handleTranscribe,
|
|
312
|
+
disabled: isTranscribing,
|
|
313
|
+
className: "mt-1 flex items-center gap-1 text-[11px] text-gray-400 opacity-0 transition-opacity hover:text-gray-600 focus:opacity-100 group-hover:opacity-100 disabled:opacity-50 dark:text-gray-500 dark:hover:text-gray-300",
|
|
314
|
+
children: [
|
|
315
|
+
isTranscribing ? /* @__PURE__ */ jsx4(Loader2, { size: 10, className: "animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx4(RefreshCw, { size: 10, "aria-hidden": true }),
|
|
316
|
+
/* @__PURE__ */ jsx4("span", { children: isTranscribing ? locales.transcribing : locales.retry })
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
] });
|
|
321
|
+
}
|
|
322
|
+
function resolveActionLabel(params) {
|
|
323
|
+
if (params.isTranscribing) return params.locales.transcribing;
|
|
324
|
+
if (params.hasRequestFailed) return params.locales.retry;
|
|
325
|
+
if (params.status === "pending") return params.locales.transcribing;
|
|
326
|
+
if (params.status === "failed") return params.locales.failed;
|
|
327
|
+
return params.locales.transcribe;
|
|
328
|
+
}
|
|
329
|
+
function TranscribeButton({
|
|
330
|
+
label,
|
|
331
|
+
isBusy,
|
|
332
|
+
isDisabled,
|
|
333
|
+
onClick
|
|
334
|
+
}) {
|
|
335
|
+
return /* @__PURE__ */ jsxs3(
|
|
336
|
+
"button",
|
|
337
|
+
{
|
|
338
|
+
"data-cv-tooltip": label,
|
|
339
|
+
"aria-label": label,
|
|
340
|
+
onClick,
|
|
341
|
+
disabled: isBusy || isDisabled,
|
|
342
|
+
className: "flex items-center gap-1.5 rounded px-1.5 py-0.5 text-[11px] text-gray-500 transition-colors hover:bg-black/5 hover:text-gray-700 disabled:opacity-60 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-gray-200",
|
|
343
|
+
children: [
|
|
344
|
+
isBusy ? /* @__PURE__ */ jsx4(Loader2, { size: 11, className: "animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx4(FileText, { size: 11, "aria-hidden": true }),
|
|
345
|
+
/* @__PURE__ */ jsx4("span", { children: label })
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
169
351
|
// src/FileIcon.tsx
|
|
170
352
|
import {
|
|
171
353
|
FileArchive,
|
|
172
354
|
FileAudio,
|
|
173
355
|
FileImage,
|
|
174
356
|
FileSpreadsheet,
|
|
175
|
-
FileText,
|
|
357
|
+
FileText as FileText2,
|
|
176
358
|
FileVideo,
|
|
177
359
|
File as FileGeneric,
|
|
178
360
|
Presentation
|
|
179
361
|
} 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";
|
|
362
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
190
363
|
var IMAGE_STYLE = { Icon: FileImage, colorClass: "text-violet-500" };
|
|
191
364
|
var VIDEO_STYLE = { Icon: FileVideo, colorClass: "text-fuchsia-500" };
|
|
192
365
|
var AUDIO_STYLE = { Icon: FileAudio, colorClass: "text-amber-500" };
|
|
193
366
|
var SHEET_STYLE = { Icon: FileSpreadsheet, colorClass: "text-green-600" };
|
|
194
|
-
var WORD_STYLE = { Icon:
|
|
367
|
+
var WORD_STYLE = { Icon: FileText2, colorClass: "text-blue-500" };
|
|
195
368
|
var SLIDES_STYLE = { Icon: Presentation, colorClass: "text-orange-600" };
|
|
196
|
-
var TEXT_STYLE = { Icon:
|
|
369
|
+
var TEXT_STYLE = { Icon: FileText2, colorClass: "text-gray-500" };
|
|
197
370
|
var EXTENSION_STYLE = {
|
|
198
|
-
pdf: { Icon:
|
|
371
|
+
pdf: { Icon: FileText2, colorClass: "text-red-500" },
|
|
199
372
|
doc: WORD_STYLE,
|
|
200
373
|
docx: WORD_STYLE,
|
|
201
374
|
xls: SHEET_STYLE,
|
|
@@ -242,7 +415,7 @@ function FileIcon({ filename, mimeType, size = 20, className }) {
|
|
|
242
415
|
const extension = resolveFileIconExtension(filename, mimeType);
|
|
243
416
|
const style = EXTENSION_STYLE[extension] ?? { Icon: FileGeneric, colorClass: "text-gray-500" };
|
|
244
417
|
const { Icon, colorClass } = style;
|
|
245
|
-
return /* @__PURE__ */
|
|
418
|
+
return /* @__PURE__ */ jsx5(Icon, { size, className: cn(colorClass, className) });
|
|
246
419
|
}
|
|
247
420
|
|
|
248
421
|
// src/lib/format.ts
|
|
@@ -274,8 +447,16 @@ function formatFileSize(bytes) {
|
|
|
274
447
|
}
|
|
275
448
|
|
|
276
449
|
// src/MediaRenderer.tsx
|
|
277
|
-
import { useState as
|
|
278
|
-
import { jsx as
|
|
450
|
+
import { useState as useState3 } from "react";
|
|
451
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
452
|
+
function documentTypeLabel(filename, mimeType) {
|
|
453
|
+
const extension = filename?.split(".").pop();
|
|
454
|
+
if (extension && extension.length <= 5 && extension !== filename) return extension.toUpperCase();
|
|
455
|
+
const subtype = mimeType?.split(";")[0]?.split("/")[1];
|
|
456
|
+
if (!subtype) return "FILE";
|
|
457
|
+
const compacto = subtype.split(".").pop() ?? subtype;
|
|
458
|
+
return compacto.slice(0, 12).toUpperCase();
|
|
459
|
+
}
|
|
279
460
|
function resolveMediaSource(message) {
|
|
280
461
|
if (message.mediaUrl) return message.mediaUrl;
|
|
281
462
|
if (message.base64) {
|
|
@@ -288,9 +469,9 @@ function hasLazyRef(message) {
|
|
|
288
469
|
return Boolean(message.uploadId || message.mediaId);
|
|
289
470
|
}
|
|
290
471
|
function useLazyMediaUrl(message, onResolveUrl) {
|
|
291
|
-
const [url, setUrl] =
|
|
292
|
-
const [loading, setLoading] =
|
|
293
|
-
const [error, setError] =
|
|
472
|
+
const [url, setUrl] = useState3(null);
|
|
473
|
+
const [loading, setLoading] = useState3(false);
|
|
474
|
+
const [error, setError] = useState3(false);
|
|
294
475
|
const load = async () => {
|
|
295
476
|
if (url || loading || !onResolveUrl) return;
|
|
296
477
|
setLoading(true);
|
|
@@ -307,68 +488,129 @@ function useLazyMediaUrl(message, onResolveUrl) {
|
|
|
307
488
|
};
|
|
308
489
|
return { url, loading, error, load };
|
|
309
490
|
}
|
|
310
|
-
function MediaRenderer({
|
|
491
|
+
function MediaRenderer({
|
|
492
|
+
message,
|
|
493
|
+
onLightbox,
|
|
494
|
+
onResolveUrl,
|
|
495
|
+
onTranscribeAudio,
|
|
496
|
+
className
|
|
497
|
+
}) {
|
|
311
498
|
const { bubble } = useConversationLocales();
|
|
312
499
|
const eagerSrc = resolveMediaSource(message);
|
|
313
500
|
const lazy = useLazyMediaUrl(message, onResolveUrl);
|
|
314
501
|
const src = eagerSrc ?? lazy.url;
|
|
315
502
|
const canLazyLoad = !eagerSrc && hasLazyRef(message) && Boolean(onResolveUrl);
|
|
316
|
-
|
|
503
|
+
function LazyMediaButton({ icon, label }) {
|
|
504
|
+
return /* @__PURE__ */ jsxs4(
|
|
505
|
+
"button",
|
|
506
|
+
{
|
|
507
|
+
"data-cv-tooltip": label,
|
|
508
|
+
"aria-label": label,
|
|
509
|
+
onClick: lazy.load,
|
|
510
|
+
disabled: lazy.loading,
|
|
511
|
+
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",
|
|
512
|
+
children: [
|
|
513
|
+
/* @__PURE__ */ jsx6("span", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-gray-600 text-white", children: icon }),
|
|
514
|
+
/* @__PURE__ */ jsx6("span", { className: "truncate text-xs text-gray-600 dark:text-gray-300", children: label })
|
|
515
|
+
]
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
}
|
|
317
519
|
switch (message.type) {
|
|
318
520
|
case "image":
|
|
319
521
|
case "sticker": {
|
|
320
522
|
if (!src && canLazyLoad) {
|
|
321
|
-
return /* @__PURE__ */
|
|
523
|
+
return /* @__PURE__ */ jsx6(
|
|
524
|
+
LazyMediaButton,
|
|
525
|
+
{
|
|
526
|
+
icon: /* @__PURE__ */ jsxs4("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
527
|
+
/* @__PURE__ */ jsx6("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
528
|
+
/* @__PURE__ */ jsx6("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
529
|
+
/* @__PURE__ */ jsx6("polyline", { points: "21 15 16 10 5 21" })
|
|
530
|
+
] }),
|
|
531
|
+
label: lazy.loading ? bubble.mediaLoading : lazy.error ? bubble.mediaRetry : bubble.viewImage
|
|
532
|
+
}
|
|
533
|
+
);
|
|
322
534
|
}
|
|
323
|
-
return /* @__PURE__ */
|
|
324
|
-
/* @__PURE__ */
|
|
325
|
-
/* @__PURE__ */
|
|
326
|
-
/* @__PURE__ */
|
|
535
|
+
return /* @__PURE__ */ jsx6("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx6("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__ */ jsx6("div", { className: "w-full h-40 bg-gray-200 flex items-center justify-center text-gray-400", children: /* @__PURE__ */ jsxs4("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
|
|
536
|
+
/* @__PURE__ */ jsx6("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
537
|
+
/* @__PURE__ */ jsx6("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
538
|
+
/* @__PURE__ */ jsx6("polyline", { points: "21 15 16 10 5 21" })
|
|
327
539
|
] }) }) });
|
|
328
540
|
}
|
|
329
541
|
case "video": {
|
|
330
542
|
if (!src && canLazyLoad) {
|
|
331
|
-
return /* @__PURE__ */
|
|
543
|
+
return /* @__PURE__ */ jsx6(
|
|
544
|
+
LazyMediaButton,
|
|
545
|
+
{
|
|
546
|
+
icon: /* @__PURE__ */ jsxs4("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
547
|
+
/* @__PURE__ */ jsx6("polygon", { points: "23 7 16 12 23 17 23 7" }),
|
|
548
|
+
/* @__PURE__ */ jsx6("rect", { x: "1", y: "5", width: "15", height: "14", rx: "2", ry: "2" })
|
|
549
|
+
] }),
|
|
550
|
+
label: lazy.loading ? bubble.mediaLoading : lazy.error ? bubble.mediaRetry : bubble.viewVideo
|
|
551
|
+
}
|
|
552
|
+
);
|
|
332
553
|
}
|
|
333
|
-
return /* @__PURE__ */
|
|
334
|
-
/* @__PURE__ */
|
|
335
|
-
/* @__PURE__ */
|
|
554
|
+
return /* @__PURE__ */ jsx6("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx6("video", { src, className: "w-full max-h-80 rounded-lg", controls: true, preload: "metadata", children: /* @__PURE__ */ jsx6("track", { kind: "captions" }) }) : /* @__PURE__ */ jsx6("div", { className: "w-full h-32 bg-gray-200 rounded-lg flex items-center justify-center text-gray-400", children: /* @__PURE__ */ jsxs4("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
|
|
555
|
+
/* @__PURE__ */ jsx6("polygon", { points: "23 7 16 12 23 17 23 7" }),
|
|
556
|
+
/* @__PURE__ */ jsx6("rect", { x: "1", y: "5", width: "15", height: "14", rx: "2", ry: "2" })
|
|
336
557
|
] }) }) });
|
|
337
558
|
}
|
|
338
559
|
case "audio": {
|
|
560
|
+
const transcriptionBlock = /* @__PURE__ */ jsx6(
|
|
561
|
+
AudioTranscription,
|
|
562
|
+
{
|
|
563
|
+
transcription: message.transcription,
|
|
564
|
+
isMine: message.direction === "outbound",
|
|
565
|
+
...onTranscribeAudio ? { onTranscribe: onTranscribeAudio } : {}
|
|
566
|
+
}
|
|
567
|
+
);
|
|
339
568
|
if (!src && canLazyLoad) {
|
|
340
|
-
return /* @__PURE__ */
|
|
569
|
+
return /* @__PURE__ */ jsxs4("div", { className: "min-w-[200px]", children: [
|
|
570
|
+
/* @__PURE__ */ jsx6(
|
|
571
|
+
LazyMediaButton,
|
|
572
|
+
{
|
|
573
|
+
icon: /* @__PURE__ */ jsx6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", className: "translate-x-0.5", children: /* @__PURE__ */ jsx6("polygon", { points: "5 3 19 12 5 21 5 3" }) }),
|
|
574
|
+
label: lazy.loading ? bubble.mediaLoading : lazy.error ? bubble.mediaRetry : bubble.listenAudio
|
|
575
|
+
}
|
|
576
|
+
),
|
|
577
|
+
transcriptionBlock
|
|
578
|
+
] });
|
|
341
579
|
}
|
|
342
|
-
return /* @__PURE__ */
|
|
580
|
+
return /* @__PURE__ */ jsxs4("div", { className: "min-w-[200px]", children: [
|
|
581
|
+
src ? /* @__PURE__ */ jsx6(AudioPlayer, { src, isMine: message.direction === "outbound" }) : /* @__PURE__ */ jsx6("div", { className: "h-12 bg-gray-200 rounded-lg flex items-center justify-center text-gray-400 text-xs", children: bubble.mediaUnavailable }),
|
|
582
|
+
transcriptionBlock
|
|
583
|
+
] });
|
|
343
584
|
}
|
|
344
585
|
case "document": {
|
|
345
|
-
const typeLabel = message.mimeType
|
|
586
|
+
const typeLabel = documentTypeLabel(message.filename, message.mimeType);
|
|
346
587
|
const sizeLabel = message.sizeBytes ? formatFileSize(message.sizeBytes) : null;
|
|
347
|
-
return /* @__PURE__ */
|
|
348
|
-
/* @__PURE__ */
|
|
349
|
-
/* @__PURE__ */
|
|
350
|
-
/* @__PURE__ */
|
|
351
|
-
/* @__PURE__ */
|
|
588
|
+
return /* @__PURE__ */ jsxs4("div", { className: cn("flex items-center gap-3 min-w-[200px]", className), children: [
|
|
589
|
+
/* @__PURE__ */ jsx6("div", { className: "w-10 h-10 bg-gray-200 rounded-lg flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx6(FileIcon, { filename: message.filename, mimeType: message.mimeType }) }),
|
|
590
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex-1 min-w-0", children: [
|
|
591
|
+
/* @__PURE__ */ jsx6("p", { className: "text-sm font-medium truncate", children: message.filename ?? bubble.untitledDocument }),
|
|
592
|
+
/* @__PURE__ */ jsx6("p", { className: "truncate text-xs text-gray-500", children: sizeLabel ? `${typeLabel} \xB7 ${sizeLabel}` : typeLabel })
|
|
352
593
|
] }),
|
|
353
|
-
src ? /* @__PURE__ */
|
|
354
|
-
/* @__PURE__ */
|
|
355
|
-
/* @__PURE__ */
|
|
356
|
-
/* @__PURE__ */
|
|
357
|
-
] }) }) : canLazyLoad ? /* @__PURE__ */
|
|
594
|
+
src ? /* @__PURE__ */ jsx6("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__ */ jsxs4("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-gray-600", children: [
|
|
595
|
+
/* @__PURE__ */ jsx6("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
596
|
+
/* @__PURE__ */ jsx6("polyline", { points: "7 10 12 15 17 10" }),
|
|
597
|
+
/* @__PURE__ */ jsx6("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
|
|
598
|
+
] }) }) : canLazyLoad ? /* @__PURE__ */ jsx6(
|
|
358
599
|
"button",
|
|
359
600
|
{
|
|
601
|
+
"data-cv-tooltip": bubble.downloadFile,
|
|
360
602
|
onClick: lazy.load,
|
|
361
603
|
disabled: lazy.loading,
|
|
362
604
|
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",
|
|
363
605
|
"aria-label": bubble.downloadFile,
|
|
364
|
-
children: /* @__PURE__ */
|
|
365
|
-
/* @__PURE__ */
|
|
366
|
-
/* @__PURE__ */
|
|
367
|
-
/* @__PURE__ */
|
|
606
|
+
children: /* @__PURE__ */ jsxs4("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-gray-600", children: [
|
|
607
|
+
/* @__PURE__ */ jsx6("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
608
|
+
/* @__PURE__ */ jsx6("polyline", { points: "7 10 12 15 17 10" }),
|
|
609
|
+
/* @__PURE__ */ jsx6("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
|
|
368
610
|
] })
|
|
369
611
|
}
|
|
370
612
|
) : null,
|
|
371
|
-
lazy.error && /* @__PURE__ */
|
|
613
|
+
lazy.error && /* @__PURE__ */ jsx6("span", { className: "text-xs text-red-500 flex-shrink-0", children: bubble.mediaError })
|
|
372
614
|
] });
|
|
373
615
|
}
|
|
374
616
|
default:
|
|
@@ -377,7 +619,7 @@ function MediaRenderer({ message, onLightbox, onResolveUrl, className }) {
|
|
|
377
619
|
}
|
|
378
620
|
|
|
379
621
|
// src/Lightbox.tsx
|
|
380
|
-
import { jsx as
|
|
622
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
381
623
|
var DEFAULT_LIGHTBOX_LABELS = {
|
|
382
624
|
imageAlt: "Imagem",
|
|
383
625
|
close: "Fechar"
|
|
@@ -385,16 +627,16 @@ var DEFAULT_LIGHTBOX_LABELS = {
|
|
|
385
627
|
function Lightbox({ imageUrl, caption, onClose, labels }) {
|
|
386
628
|
const imageAltLabel = labels?.imageAlt ?? DEFAULT_LIGHTBOX_LABELS.imageAlt;
|
|
387
629
|
const closeLabel = labels?.close ?? DEFAULT_LIGHTBOX_LABELS.close;
|
|
388
|
-
return /* @__PURE__ */
|
|
389
|
-
/* @__PURE__ */
|
|
390
|
-
caption && /* @__PURE__ */
|
|
391
|
-
/* @__PURE__ */
|
|
630
|
+
return /* @__PURE__ */ jsx7("div", { className: "fixed inset-0 z-50 bg-black/85 flex items-center justify-center p-4", onClick: onClose, children: /* @__PURE__ */ jsxs5("div", { className: "max-w-[90vw] max-h-[90vh] flex flex-col items-center", onClick: (e) => e.stopPropagation(), children: [
|
|
631
|
+
/* @__PURE__ */ jsx7("img", { src: imageUrl, alt: caption ?? imageAltLabel, className: "max-w-full max-h-[80vh] object-contain rounded-lg" }),
|
|
632
|
+
caption && /* @__PURE__ */ jsx7("p", { className: "text-white text-sm mt-3 text-center", children: caption }),
|
|
633
|
+
/* @__PURE__ */ jsx7("button", { "data-cv-tooltip": closeLabel, "aria-label": closeLabel, onClick: onClose, className: "mt-4 px-4 py-2 bg-white/20 text-white rounded-lg hover:bg-white/30 transition-colors", children: closeLabel })
|
|
392
634
|
] }) });
|
|
393
635
|
}
|
|
394
636
|
|
|
395
637
|
// src/InteractiveMessage.tsx
|
|
396
|
-
import { useState as
|
|
397
|
-
import { jsx as
|
|
638
|
+
import { useState as useState4 } from "react";
|
|
639
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
398
640
|
var FORMATTING_CLASSES = "[&_strong]:font-bold [&_em]:italic [&_del]:line-through";
|
|
399
641
|
var DEFAULT_INTERACTIVE_MESSAGE_LABELS = {
|
|
400
642
|
openList: "Ver op\xE7\xF5es"
|
|
@@ -407,14 +649,14 @@ function collectButtons(payload) {
|
|
|
407
649
|
}
|
|
408
650
|
function InteractiveMessage({ payload, onSelect, labels, className }) {
|
|
409
651
|
const openListLabel = labels?.openList ?? DEFAULT_INTERACTIVE_MESSAGE_LABELS.openList;
|
|
410
|
-
const [isListOpen, setIsListOpen] =
|
|
652
|
+
const [isListOpen, setIsListOpen] = useState4(false);
|
|
411
653
|
const buttons = collectButtons(payload);
|
|
412
654
|
const sections = payload.action?.sections ?? [];
|
|
413
655
|
const hasRows = collectRows(payload).length > 0;
|
|
414
656
|
const isInteractable = onSelect !== void 0;
|
|
415
|
-
return /* @__PURE__ */
|
|
416
|
-
payload.header?.text ? /* @__PURE__ */
|
|
417
|
-
payload.body?.text ? /* @__PURE__ */
|
|
657
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn("flex flex-col gap-1", className), children: [
|
|
658
|
+
payload.header?.text ? /* @__PURE__ */ jsx8("p", { className: cn("text-sm font-semibold text-gray-900 dark:text-gray-100", FORMATTING_CLASSES), children: parseWhatsAppFormatting(payload.header.text) }) : null,
|
|
659
|
+
payload.body?.text ? /* @__PURE__ */ jsx8(
|
|
418
660
|
"p",
|
|
419
661
|
{
|
|
420
662
|
className: cn(
|
|
@@ -424,10 +666,12 @@ function InteractiveMessage({ payload, onSelect, labels, className }) {
|
|
|
424
666
|
children: parseWhatsAppFormatting(payload.body.text)
|
|
425
667
|
}
|
|
426
668
|
) : null,
|
|
427
|
-
payload.footer?.text ? /* @__PURE__ */
|
|
428
|
-
buttons.length > 0 ? /* @__PURE__ */
|
|
669
|
+
payload.footer?.text ? /* @__PURE__ */ jsx8("p", { className: cn("text-xs text-gray-500 dark:text-gray-400", FORMATTING_CLASSES), children: parseWhatsAppFormatting(payload.footer.text) }) : null,
|
|
670
|
+
buttons.length > 0 ? /* @__PURE__ */ jsx8("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__ */ jsx8(
|
|
429
671
|
"button",
|
|
430
672
|
{
|
|
673
|
+
"data-cv-tooltip": button.title,
|
|
674
|
+
"aria-label": button.title,
|
|
431
675
|
type: "button",
|
|
432
676
|
disabled: !isInteractable,
|
|
433
677
|
onClick: () => onSelect?.({ kind: "button", option: button }),
|
|
@@ -436,10 +680,12 @@ function InteractiveMessage({ payload, onSelect, labels, className }) {
|
|
|
436
680
|
},
|
|
437
681
|
button.id
|
|
438
682
|
)) }) : null,
|
|
439
|
-
hasRows ? /* @__PURE__ */
|
|
440
|
-
/* @__PURE__ */
|
|
683
|
+
hasRows ? /* @__PURE__ */ jsxs6("div", { className: "mt-1 border-t border-gray-200 pt-1 dark:border-gray-700", children: [
|
|
684
|
+
/* @__PURE__ */ jsxs6(
|
|
441
685
|
"button",
|
|
442
686
|
{
|
|
687
|
+
"data-cv-tooltip": payload.action?.button ?? openListLabel,
|
|
688
|
+
"aria-label": payload.action?.button ?? openListLabel,
|
|
443
689
|
type: "button",
|
|
444
690
|
onClick: () => setIsListOpen((open) => !open),
|
|
445
691
|
"aria-expanded": isListOpen,
|
|
@@ -450,18 +696,20 @@ function InteractiveMessage({ payload, onSelect, labels, className }) {
|
|
|
450
696
|
]
|
|
451
697
|
}
|
|
452
698
|
),
|
|
453
|
-
isListOpen ? /* @__PURE__ */
|
|
454
|
-
section.title ? /* @__PURE__ */
|
|
455
|
-
(section.rows ?? []).map((row) => /* @__PURE__ */
|
|
699
|
+
isListOpen ? /* @__PURE__ */ jsx8("div", { className: "mt-1 flex flex-col gap-1", children: sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs6("div", { className: "flex flex-col", children: [
|
|
700
|
+
section.title ? /* @__PURE__ */ jsx8("p", { className: "px-3 py-1 text-xs font-semibold uppercase text-gray-500 dark:text-gray-400", children: section.title }) : null,
|
|
701
|
+
(section.rows ?? []).map((row) => /* @__PURE__ */ jsxs6(
|
|
456
702
|
"button",
|
|
457
703
|
{
|
|
704
|
+
"data-cv-tooltip": row.title,
|
|
705
|
+
"aria-label": row.title,
|
|
458
706
|
type: "button",
|
|
459
707
|
disabled: !isInteractable,
|
|
460
708
|
onClick: () => onSelect?.({ kind: "list", option: row }),
|
|
461
709
|
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",
|
|
462
710
|
children: [
|
|
463
|
-
/* @__PURE__ */
|
|
464
|
-
row.description ? /* @__PURE__ */
|
|
711
|
+
/* @__PURE__ */ jsx8("span", { className: "block", children: row.title }),
|
|
712
|
+
row.description ? /* @__PURE__ */ jsx8("span", { className: "block text-xs text-gray-500 dark:text-gray-400", children: row.description }) : null
|
|
465
713
|
]
|
|
466
714
|
},
|
|
467
715
|
row.id
|
|
@@ -484,24 +732,25 @@ function createMediaUrlResolver(api) {
|
|
|
484
732
|
}
|
|
485
733
|
|
|
486
734
|
// src/providers/ConversationsProvider.tsx
|
|
487
|
-
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
488
|
-
import { jsx as
|
|
735
|
+
import { createContext as createContext2, useContext as useContext2, useMemo } from "react";
|
|
736
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
489
737
|
var ConversationsContext = createContext2(null);
|
|
490
738
|
function ConversationsProvider({
|
|
491
739
|
api,
|
|
492
740
|
sse,
|
|
493
741
|
children
|
|
494
742
|
}) {
|
|
495
|
-
|
|
743
|
+
const value = useMemo(() => ({ api, sse }), [api, sse]);
|
|
744
|
+
return /* @__PURE__ */ jsx9(ConversationsContext.Provider, { value, children });
|
|
496
745
|
}
|
|
497
746
|
function useConversations() {
|
|
498
747
|
return useContext2(ConversationsContext);
|
|
499
748
|
}
|
|
500
749
|
|
|
501
750
|
// src/MessageBubble.tsx
|
|
502
|
-
import { useMemo, useState as
|
|
503
|
-
import { Check } from "lucide-react";
|
|
504
|
-
import { Fragment, jsx as
|
|
751
|
+
import { useMemo as useMemo2, useState as useState5 } from "react";
|
|
752
|
+
import { Check as Check2 } from "lucide-react";
|
|
753
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
505
754
|
var BUBBLE_COLOR = {
|
|
506
755
|
agent: "bg-[#d9fdd3] dark:bg-[#005c4b]",
|
|
507
756
|
bot: "bg-[#d7f0ec] dark:bg-[#0a3d3a]",
|
|
@@ -518,40 +767,51 @@ function MessageBubble({
|
|
|
518
767
|
onToggleSelect,
|
|
519
768
|
onResolveMediaUrl,
|
|
520
769
|
onInteractiveSelect,
|
|
770
|
+
onTranscribeAudio,
|
|
521
771
|
className
|
|
522
772
|
}) {
|
|
523
773
|
const { bubble, selection } = useConversationLocales();
|
|
524
|
-
const [lightboxSrc, setLightboxSrc] =
|
|
774
|
+
const [lightboxSrc, setLightboxSrc] = useState5(null);
|
|
525
775
|
const context = useConversations();
|
|
526
|
-
const resolveMediaUrl =
|
|
776
|
+
const resolveMediaUrl = useMemo2(
|
|
527
777
|
() => onResolveMediaUrl ?? (context?.api ? createMediaUrlResolver(context.api) : void 0),
|
|
528
778
|
[onResolveMediaUrl, context?.api]
|
|
529
779
|
);
|
|
780
|
+
const requestTranscription = useMemo2(() => {
|
|
781
|
+
const transcribe = onTranscribeAudio ?? context?.api?.transcribeAudio?.bind(context.api);
|
|
782
|
+
if (!transcribe) return void 0;
|
|
783
|
+
return () => transcribe(message.id);
|
|
784
|
+
}, [onTranscribeAudio, context?.api, message.id]);
|
|
530
785
|
const bubbleColor = BUBBLE_COLOR[message.sender] ?? BUBBLE_COLOR.customer;
|
|
531
786
|
const hasError = message.status === "failed";
|
|
532
787
|
const isMedia = MEDIA_TYPES.has(message.type);
|
|
533
788
|
const isTemplate = message.type === "template";
|
|
534
789
|
const isInteractive = message.type === "interactive";
|
|
790
|
+
const mediaCaption = isMedia ? [message.caption, message.content].find((text) => {
|
|
791
|
+
const trimmed = text?.trim();
|
|
792
|
+
return trimmed && trimmed !== message.filename;
|
|
793
|
+
}) : void 0;
|
|
535
794
|
const displayName = message.sender === "agent" && senderName ? senderName : bubble[message.sender] ?? message.sender;
|
|
536
795
|
const tooltipText = message.status === "read" && message.readAt ? `${bubble.readAt}${formatDateTime(message.readAt)}` : message.status === "failed" ? bubble.windowExpired : void 0;
|
|
537
796
|
const tailCornerClass = isFirstInGroup ? isMine ? "rounded-tr-md" : "rounded-tl-md" : "";
|
|
538
|
-
const checkbox = /* @__PURE__ */
|
|
797
|
+
const checkbox = /* @__PURE__ */ jsx10(
|
|
539
798
|
"button",
|
|
540
799
|
{
|
|
541
800
|
onClick: (e) => {
|
|
542
801
|
e.stopPropagation();
|
|
543
802
|
onToggleSelect?.();
|
|
544
803
|
},
|
|
545
|
-
|
|
804
|
+
"data-cv-tooltip": selection.select,
|
|
805
|
+
"aria-label": selection.select,
|
|
546
806
|
className: `
|
|
547
807
|
flex-shrink-0 self-end mb-1 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all
|
|
548
808
|
${isSelected ? "bg-teal-600 border-teal-600" : "bg-white/80 dark:bg-black/40 border-black/20 dark:border-white/30"}
|
|
549
809
|
${isSelecting ? "opacity-100" : "opacity-0 group-hover:opacity-100"}
|
|
550
810
|
`,
|
|
551
|
-
children: isSelected && /* @__PURE__ */
|
|
811
|
+
children: isSelected && /* @__PURE__ */ jsx10(Check2, { size: 12, className: "text-white", strokeWidth: 3 })
|
|
552
812
|
}
|
|
553
813
|
);
|
|
554
|
-
return /* @__PURE__ */
|
|
814
|
+
return /* @__PURE__ */ jsxs7(
|
|
555
815
|
"div",
|
|
556
816
|
{
|
|
557
817
|
className: cn(
|
|
@@ -562,7 +822,7 @@ function MessageBubble({
|
|
|
562
822
|
),
|
|
563
823
|
children: [
|
|
564
824
|
isMine && checkbox,
|
|
565
|
-
/* @__PURE__ */
|
|
825
|
+
/* @__PURE__ */ jsxs7(
|
|
566
826
|
"div",
|
|
567
827
|
{
|
|
568
828
|
onClick: isSelecting ? onToggleSelect : void 0,
|
|
@@ -575,48 +835,60 @@ function MessageBubble({
|
|
|
575
835
|
relative
|
|
576
836
|
`,
|
|
577
837
|
children: [
|
|
578
|
-
isMine && isFirstInGroup && (message.sender === "bot" || message.sender === "agent" && senderName) && /* @__PURE__ */
|
|
579
|
-
message.moderation?.isOffensive && /* @__PURE__ */
|
|
838
|
+
isMine && isFirstInGroup && (message.sender === "bot" || message.sender === "agent" && senderName) && /* @__PURE__ */ jsx10("div", { className: "text-xs font-semibold mb-0.5 text-teal-700 dark:text-teal-400", children: displayName }),
|
|
839
|
+
message.moderation?.isOffensive && /* @__PURE__ */ jsxs7(
|
|
580
840
|
"div",
|
|
581
841
|
{
|
|
582
842
|
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",
|
|
583
|
-
|
|
843
|
+
"data-cv-tooltip": message.moderation.terms.length > 0 ? message.moderation.terms.join(", ") : void 0,
|
|
584
844
|
children: [
|
|
585
|
-
/* @__PURE__ */
|
|
586
|
-
/* @__PURE__ */
|
|
845
|
+
/* @__PURE__ */ jsx10("span", { "aria-hidden": true, children: "\u26A0\uFE0F" }),
|
|
846
|
+
/* @__PURE__ */ jsx10("span", { children: bubble.moderationFlagged })
|
|
587
847
|
]
|
|
588
848
|
}
|
|
589
849
|
),
|
|
590
|
-
isMedia ? /* @__PURE__ */
|
|
850
|
+
isMedia ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
851
|
+
/* @__PURE__ */ jsx10(
|
|
852
|
+
MediaRenderer,
|
|
853
|
+
{
|
|
854
|
+
message,
|
|
855
|
+
onLightbox: setLightboxSrc,
|
|
856
|
+
onResolveUrl: resolveMediaUrl,
|
|
857
|
+
...requestTranscription ? { onTranscribeAudio: requestTranscription } : {}
|
|
858
|
+
}
|
|
859
|
+
),
|
|
860
|
+
mediaCaption ? /* @__PURE__ */ jsx10("div", { className: "mt-1 text-sm text-gray-900 dark:text-gray-100 whitespace-pre-wrap break-words leading-[19px]", children: parseWhatsAppFormatting(mediaCaption) }) : null
|
|
861
|
+
] }) : isInteractive && message.payload ? (
|
|
591
862
|
// O texto da mensagem interativa mora dentro do payload (`body.text`), e `content` guarda
|
|
592
863
|
// só uma cópia achatada para busca — renderizar `content` aqui duplicaria o corpo.
|
|
593
|
-
/* @__PURE__ */
|
|
594
|
-
) : /* @__PURE__ */
|
|
595
|
-
isTemplate && /* @__PURE__ */
|
|
596
|
-
/* @__PURE__ */
|
|
597
|
-
/* @__PURE__ */
|
|
864
|
+
/* @__PURE__ */ jsx10(InteractiveMessage, { payload: message.payload, onSelect: onInteractiveSelect })
|
|
865
|
+
) : /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
866
|
+
isTemplate && /* @__PURE__ */ jsxs7("p", { className: "text-xs text-gray-500 dark:text-gray-400 flex items-center gap-1 mb-0.5", children: [
|
|
867
|
+
/* @__PURE__ */ jsx10("span", { children: "\u{1F4E8}" }),
|
|
868
|
+
/* @__PURE__ */ jsxs7("span", { className: "font-medium", children: [
|
|
598
869
|
bubble.templateLabel,
|
|
599
870
|
message.templateName ? ` \u2014 ${message.templateName}` : ""
|
|
600
871
|
] })
|
|
601
872
|
] }),
|
|
602
|
-
/* @__PURE__ */
|
|
873
|
+
/* @__PURE__ */ jsx10("div", { className: "text-sm text-gray-900 dark:text-gray-100 whitespace-pre-wrap break-words leading-[19px]", children: parseWhatsAppFormatting(message.content ?? "") })
|
|
603
874
|
] }),
|
|
604
|
-
/* @__PURE__ */
|
|
605
|
-
/* @__PURE__ */
|
|
606
|
-
isMine && message.status && /* @__PURE__ */
|
|
875
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-end gap-1 mt-0.5 select-none", children: [
|
|
876
|
+
/* @__PURE__ */ jsx10("span", { className: "text-xs text-black/40 dark:text-white/40 font-medium", children: formatTimestamp(message.timestamp) }),
|
|
877
|
+
isMine && message.status && /* @__PURE__ */ jsx10(StatusTicks, { status: message.status, title: tooltipText })
|
|
607
878
|
] })
|
|
608
879
|
]
|
|
609
880
|
}
|
|
610
881
|
),
|
|
611
882
|
!isMine && checkbox,
|
|
612
|
-
lightboxSrc && /* @__PURE__ */
|
|
883
|
+
lightboxSrc && /* @__PURE__ */ jsx10(Lightbox, { imageUrl: lightboxSrc, onClose: () => setLightboxSrc(null) })
|
|
613
884
|
]
|
|
614
885
|
}
|
|
615
886
|
);
|
|
616
887
|
}
|
|
617
888
|
|
|
618
889
|
// src/Wallpaper.tsx
|
|
619
|
-
import {
|
|
890
|
+
import { forwardRef } from "react";
|
|
891
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
620
892
|
function doodlePattern(strokeColor, opacity) {
|
|
621
893
|
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>`;
|
|
622
894
|
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
|
|
@@ -633,17 +905,21 @@ var DARK_WALLPAPER = {
|
|
|
633
905
|
backgroundRepeat: "repeat",
|
|
634
906
|
backgroundSize: "100px 100px"
|
|
635
907
|
};
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
908
|
+
var ConversationWallpaper = forwardRef(
|
|
909
|
+
function ConversationWallpaper2({ children, className, style, onScroll }, ref) {
|
|
910
|
+
const isDark = useIsDarkTheme();
|
|
911
|
+
return /* @__PURE__ */ jsx11(
|
|
912
|
+
"div",
|
|
913
|
+
{
|
|
914
|
+
ref,
|
|
915
|
+
onScroll,
|
|
916
|
+
className: cn("cv-wallpaper", className),
|
|
917
|
+
style: { ...isDark ? DARK_WALLPAPER : LIGHT_WALLPAPER, ...style },
|
|
918
|
+
children
|
|
919
|
+
}
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
);
|
|
647
923
|
|
|
648
924
|
// src/emojiCatalog.ts
|
|
649
925
|
var EMOJI_CATEGORIES = [
|
|
@@ -800,8 +1076,8 @@ function searchEmojis(query) {
|
|
|
800
1076
|
}
|
|
801
1077
|
|
|
802
1078
|
// src/EmojiPicker.tsx
|
|
803
|
-
import { useState as
|
|
804
|
-
import { jsx as
|
|
1079
|
+
import { useState as useState6, useCallback as useCallback2, useMemo as useMemo3 } from "react";
|
|
1080
|
+
import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
805
1081
|
var DEFAULT_EMOJI_PICKER_LABELS = {
|
|
806
1082
|
search: "Buscar emoji",
|
|
807
1083
|
noResults: "Nenhum emoji encontrado"
|
|
@@ -809,21 +1085,21 @@ var DEFAULT_EMOJI_PICKER_LABELS = {
|
|
|
809
1085
|
var EmojiPicker = ({ onSelect, labels, className = "" }) => {
|
|
810
1086
|
const searchLabel = labels?.search ?? DEFAULT_EMOJI_PICKER_LABELS.search;
|
|
811
1087
|
const noResultsLabel = labels?.noResults ?? DEFAULT_EMOJI_PICKER_LABELS.noResults;
|
|
812
|
-
const [activeCategory, setActiveCategory] =
|
|
813
|
-
const [query, setQuery] =
|
|
814
|
-
const handleSelect =
|
|
1088
|
+
const [activeCategory, setActiveCategory] = useState6(0);
|
|
1089
|
+
const [query, setQuery] = useState6("");
|
|
1090
|
+
const handleSelect = useCallback2(
|
|
815
1091
|
(emoji) => {
|
|
816
1092
|
onSelect(emoji);
|
|
817
1093
|
},
|
|
818
1094
|
[onSelect]
|
|
819
1095
|
);
|
|
820
1096
|
const isSearching = query.trim().length > 0;
|
|
821
|
-
const visibleEntries =
|
|
1097
|
+
const visibleEntries = useMemo3(
|
|
822
1098
|
() => isSearching ? searchEmojis(query) : EMOJI_CATEGORIES[activeCategory].entries,
|
|
823
1099
|
[isSearching, query, activeCategory]
|
|
824
1100
|
);
|
|
825
|
-
return /* @__PURE__ */
|
|
826
|
-
/* @__PURE__ */
|
|
1101
|
+
return /* @__PURE__ */ jsxs8("div", { className: `bg-white border border-gray-200 rounded-lg shadow-lg overflow-hidden ${className}`, children: [
|
|
1102
|
+
/* @__PURE__ */ jsx12("div", { className: "p-2 border-b border-gray-200", children: /* @__PURE__ */ jsx12(
|
|
827
1103
|
"input",
|
|
828
1104
|
{
|
|
829
1105
|
type: "search",
|
|
@@ -834,9 +1110,11 @@ var EmojiPicker = ({ onSelect, labels, className = "" }) => {
|
|
|
834
1110
|
className: "w-full rounded-md border border-gray-200 px-2 py-1.5 text-sm outline-none focus:border-blue-500"
|
|
835
1111
|
}
|
|
836
1112
|
) }),
|
|
837
|
-
isSearching ? null : /* @__PURE__ */
|
|
1113
|
+
isSearching ? null : /* @__PURE__ */ jsx12("div", { className: "flex border-b border-gray-200 overflow-x-auto", children: EMOJI_CATEGORIES.map((category, index) => /* @__PURE__ */ jsxs8(
|
|
838
1114
|
"button",
|
|
839
1115
|
{
|
|
1116
|
+
"data-cv-tooltip": category.name,
|
|
1117
|
+
"aria-label": category.name,
|
|
840
1118
|
onClick: () => setActiveCategory(index),
|
|
841
1119
|
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"}`,
|
|
842
1120
|
children: [
|
|
@@ -847,13 +1125,13 @@ var EmojiPicker = ({ onSelect, labels, className = "" }) => {
|
|
|
847
1125
|
},
|
|
848
1126
|
category.name
|
|
849
1127
|
)) }),
|
|
850
|
-
visibleEntries.length === 0 ? /* @__PURE__ */
|
|
1128
|
+
visibleEntries.length === 0 ? /* @__PURE__ */ jsx12("p", { className: "px-3 py-6 text-center text-sm text-gray-500", children: noResultsLabel }) : /* @__PURE__ */ jsx12("div", { className: "grid grid-cols-10 gap-0.5 p-2 max-h-[240px] overflow-y-auto", children: visibleEntries.map((entry) => /* @__PURE__ */ jsx12(
|
|
851
1129
|
"button",
|
|
852
1130
|
{
|
|
853
1131
|
onClick: () => handleSelect(entry.emoji),
|
|
854
1132
|
className: "w-8 h-8 flex items-center justify-center text-lg hover:bg-gray-100 rounded transition-colors cursor-pointer",
|
|
855
1133
|
"aria-label": entry.emoji,
|
|
856
|
-
|
|
1134
|
+
"data-cv-tooltip": entry.keywords[0],
|
|
857
1135
|
children: entry.emoji
|
|
858
1136
|
},
|
|
859
1137
|
entry.emoji
|
|
@@ -861,13 +1139,189 @@ var EmojiPicker = ({ onSelect, labels, className = "" }) => {
|
|
|
861
1139
|
] });
|
|
862
1140
|
};
|
|
863
1141
|
|
|
1142
|
+
// src/AudioRecorderButton.tsx
|
|
1143
|
+
import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState7 } from "react";
|
|
1144
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1145
|
+
var DEFAULT_AUDIO_RECORDER_BUTTON_LABELS = {
|
|
1146
|
+
start: "Gravar \xE1udio",
|
|
1147
|
+
stop: "Parar grava\xE7\xE3o",
|
|
1148
|
+
unsupported: "Este navegador n\xE3o grava \xE1udio.",
|
|
1149
|
+
denied: "Sem permiss\xE3o para usar o microfone.",
|
|
1150
|
+
review: "Ou\xE7a antes de enviar",
|
|
1151
|
+
send: "Enviar \xE1udio",
|
|
1152
|
+
discard: "Descartar \xE1udio",
|
|
1153
|
+
empty: "Nada foi captado pelo microfone."
|
|
1154
|
+
};
|
|
1155
|
+
var DEFAULT_MAX_RECORDING_MILLISECONDS = 5 * 60 * 1e3;
|
|
1156
|
+
var RECORDING_FORMATS = [
|
|
1157
|
+
{ mimeType: "audio/ogg;codecs=opus", uploadMimeType: "audio/ogg", extension: "ogg" },
|
|
1158
|
+
{ mimeType: "audio/mp4", uploadMimeType: "audio/mp4", extension: "m4a" },
|
|
1159
|
+
{ mimeType: "audio/webm", uploadMimeType: "audio/webm", extension: "webm" }
|
|
1160
|
+
];
|
|
1161
|
+
function resolveRecordingFormat() {
|
|
1162
|
+
if (typeof MediaRecorder === "undefined") return void 0;
|
|
1163
|
+
if (typeof MediaRecorder.isTypeSupported !== "function") return RECORDING_FORMATS[0];
|
|
1164
|
+
return RECORDING_FORMATS.find((format) => MediaRecorder.isTypeSupported(format.mimeType));
|
|
1165
|
+
}
|
|
1166
|
+
function AudioRecorderButton({
|
|
1167
|
+
onRecorded,
|
|
1168
|
+
onFailure,
|
|
1169
|
+
onRecordingChange,
|
|
1170
|
+
reviewBeforeSend = true,
|
|
1171
|
+
maxDurationMilliseconds = DEFAULT_MAX_RECORDING_MILLISECONDS,
|
|
1172
|
+
labels,
|
|
1173
|
+
disabled
|
|
1174
|
+
}) {
|
|
1175
|
+
const labelOf = (key) => labels?.[key] ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS[key];
|
|
1176
|
+
const [isRecording, setIsRecording] = useState7(false);
|
|
1177
|
+
const [pending, setPending] = useState7(void 0);
|
|
1178
|
+
const recorderRef = useRef2(null);
|
|
1179
|
+
const autoStopRef = useRef2(void 0);
|
|
1180
|
+
const discard = useCallback3(() => {
|
|
1181
|
+
setPending((current) => {
|
|
1182
|
+
if (current) URL.revokeObjectURL(current.objectURL);
|
|
1183
|
+
return void 0;
|
|
1184
|
+
});
|
|
1185
|
+
}, []);
|
|
1186
|
+
useEffect2(() => discard, [discard]);
|
|
1187
|
+
const confirm = useCallback3(() => {
|
|
1188
|
+
if (!pending) return;
|
|
1189
|
+
void onRecorded(pending.file);
|
|
1190
|
+
discard();
|
|
1191
|
+
}, [discard, onRecorded, pending]);
|
|
1192
|
+
const stop = useCallback3(() => {
|
|
1193
|
+
recorderRef.current?.stop();
|
|
1194
|
+
}, []);
|
|
1195
|
+
const start = useCallback3(async () => {
|
|
1196
|
+
const format = resolveRecordingFormat();
|
|
1197
|
+
if (!format || !navigator.mediaDevices?.getUserMedia) {
|
|
1198
|
+
onFailure?.(labels?.unsupported ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.unsupported);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
try {
|
|
1202
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
1203
|
+
const recorder = new MediaRecorder(stream, { mimeType: format.mimeType });
|
|
1204
|
+
const chunks = [];
|
|
1205
|
+
recorder.addEventListener("dataavailable", (event) => {
|
|
1206
|
+
if (event.data.size > 0) chunks.push(event.data);
|
|
1207
|
+
});
|
|
1208
|
+
recorder.addEventListener("stop", () => {
|
|
1209
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
1210
|
+
clearTimeout(autoStopRef.current);
|
|
1211
|
+
setIsRecording(false);
|
|
1212
|
+
onRecordingChange?.(false);
|
|
1213
|
+
recorderRef.current = null;
|
|
1214
|
+
const blob = new Blob(chunks, { type: format.uploadMimeType });
|
|
1215
|
+
const file = new File([blob], `audio-${Date.now()}.${format.extension}`, {
|
|
1216
|
+
type: format.uploadMimeType
|
|
1217
|
+
});
|
|
1218
|
+
if (blob.size === 0) {
|
|
1219
|
+
onFailure?.(labelOf("empty"));
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
if (!reviewBeforeSend) {
|
|
1223
|
+
void onRecorded(file);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
setPending({ file, objectURL: URL.createObjectURL(blob) });
|
|
1227
|
+
});
|
|
1228
|
+
recorderRef.current = recorder;
|
|
1229
|
+
recorder.start();
|
|
1230
|
+
autoStopRef.current = setTimeout(() => recorder.stop(), maxDurationMilliseconds);
|
|
1231
|
+
setIsRecording(true);
|
|
1232
|
+
onRecordingChange?.(true);
|
|
1233
|
+
} catch {
|
|
1234
|
+
onFailure?.(labels?.denied ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS.denied);
|
|
1235
|
+
}
|
|
1236
|
+
}, [maxDurationMilliseconds, onFailure, onRecorded, onRecordingChange, reviewBeforeSend]);
|
|
1237
|
+
const toggleLabel = isRecording ? labelOf("stop") : labelOf("start");
|
|
1238
|
+
return (
|
|
1239
|
+
/* O painel de revisão flutua sobre o botão em vez de ocupar espaço na barra: o microfone mora
|
|
1240
|
+
na caixa do botão de enviar, e empurrar o composer para cima a cada gravação faria a
|
|
1241
|
+
conversa saltar. */
|
|
1242
|
+
/* @__PURE__ */ jsxs9("div", { className: "relative flex-shrink-0", children: [
|
|
1243
|
+
pending && /* @__PURE__ */ jsxs9(
|
|
1244
|
+
"div",
|
|
1245
|
+
{
|
|
1246
|
+
role: "group",
|
|
1247
|
+
"aria-label": labelOf("review"),
|
|
1248
|
+
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",
|
|
1249
|
+
children: [
|
|
1250
|
+
/* @__PURE__ */ jsx13("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx13(AudioPlayer, { src: pending.objectURL }) }),
|
|
1251
|
+
/* @__PURE__ */ jsx13(
|
|
1252
|
+
"button",
|
|
1253
|
+
{
|
|
1254
|
+
type: "button",
|
|
1255
|
+
onClick: discard,
|
|
1256
|
+
"data-cv-tooltip": labelOf("discard"),
|
|
1257
|
+
"aria-label": labelOf("discard"),
|
|
1258
|
+
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",
|
|
1259
|
+
children: /* @__PURE__ */ jsxs9("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
1260
|
+
/* @__PURE__ */ jsx13("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1261
|
+
/* @__PURE__ */ jsx13("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1262
|
+
] })
|
|
1263
|
+
}
|
|
1264
|
+
),
|
|
1265
|
+
/* @__PURE__ */ jsx13(
|
|
1266
|
+
"button",
|
|
1267
|
+
{
|
|
1268
|
+
type: "button",
|
|
1269
|
+
onClick: confirm,
|
|
1270
|
+
"data-cv-tooltip": labelOf("send"),
|
|
1271
|
+
"aria-label": labelOf("send"),
|
|
1272
|
+
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",
|
|
1273
|
+
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" }) })
|
|
1274
|
+
}
|
|
1275
|
+
)
|
|
1276
|
+
]
|
|
1277
|
+
}
|
|
1278
|
+
),
|
|
1279
|
+
/* @__PURE__ */ jsx13(
|
|
1280
|
+
"button",
|
|
1281
|
+
{
|
|
1282
|
+
type: "button",
|
|
1283
|
+
disabled: disabled || pending !== void 0,
|
|
1284
|
+
onClick: () => isRecording ? stop() : void start(),
|
|
1285
|
+
"data-cv-tooltip": toggleLabel,
|
|
1286
|
+
"aria-label": toggleLabel,
|
|
1287
|
+
"aria-pressed": isRecording,
|
|
1288
|
+
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"}`,
|
|
1289
|
+
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: [
|
|
1290
|
+
/* @__PURE__ */ jsx13("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z" }),
|
|
1291
|
+
/* @__PURE__ */ jsx13("path", { d: "M19 11a7 7 0 0 1-14 0" }),
|
|
1292
|
+
/* @__PURE__ */ jsx13("line", { x1: "12", y1: "18", x2: "12", y2: "22" })
|
|
1293
|
+
] })
|
|
1294
|
+
}
|
|
1295
|
+
)
|
|
1296
|
+
] })
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// src/MessageComposer.tsx
|
|
1301
|
+
import { useState as useState8, useRef as useRef3, useCallback as useCallback4 } from "react";
|
|
1302
|
+
|
|
1303
|
+
// src/composer.constant.ts
|
|
1304
|
+
var COMPOSER_BAR_CLASS = "cv-composer-bar";
|
|
1305
|
+
var COMPOSER_TOOL_BUTTON_CLASS = "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full transition-colors hover:bg-teal-50 hover:text-teal-600 dark:hover:bg-teal-900/30 dark:hover:text-teal-400";
|
|
1306
|
+
var COMPOSER_TOOL_BUTTON_ACTIVE_CLASS = "bg-teal-50 text-teal-600 dark:bg-teal-900/40 dark:text-teal-400";
|
|
1307
|
+
var COMPOSER_TOOL_BUTTON_IDLE_CLASS = "text-gray-400 dark:text-gray-500";
|
|
1308
|
+
var COMPOSER_MONOSPACE_CLASS = "bg-black/5 dark:bg-white/10 rounded px-0.5 font-mono text-sm";
|
|
1309
|
+
var COMPOSER_COMPACT_WIDTH = 480;
|
|
1310
|
+
var QUICK_REPLY_PILL_CLASS = "whitespace-nowrap rounded-full border border-gray-200 bg-white px-3 py-1.5 text-xs text-gray-600 shadow-sm 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 dark:hover:border-teal-800 dark:hover:bg-teal-950/40 dark:hover:text-teal-400";
|
|
1311
|
+
|
|
864
1312
|
// src/MessageComposer.tsx
|
|
865
|
-
import {
|
|
866
|
-
|
|
1313
|
+
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1314
|
+
function applyQuickReplyVariables(template, variables = {}) {
|
|
1315
|
+
return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, chave) => variables[chave] ?? "");
|
|
1316
|
+
}
|
|
1317
|
+
function resolveQuickReply(quickReply, variables = {}) {
|
|
1318
|
+
return typeof quickReply.text === "function" ? quickReply.text(variables) : applyQuickReplyVariables(quickReply.text, variables);
|
|
1319
|
+
}
|
|
867
1320
|
var DEFAULT_MESSAGE_COMPOSER_LABELS = {
|
|
868
1321
|
emoji: "Emoji",
|
|
869
1322
|
attach: "Anexar",
|
|
870
|
-
send: "Enviar"
|
|
1323
|
+
send: "Enviar",
|
|
1324
|
+
removeAttachment: "Remover anexo"
|
|
871
1325
|
};
|
|
872
1326
|
var DEFAULT_ACCEPTED_FILE_TYPES = [
|
|
873
1327
|
"image/jpeg,image/png,image/webp",
|
|
@@ -884,11 +1338,14 @@ var MessageComposer = ({
|
|
|
884
1338
|
onAttach,
|
|
885
1339
|
value: externalValue,
|
|
886
1340
|
onChange: externalOnChange,
|
|
1341
|
+
quickReplies,
|
|
1342
|
+
quickReplyVariables,
|
|
887
1343
|
features,
|
|
888
1344
|
placeholder = "Digite uma mensagem...",
|
|
889
1345
|
maxLength,
|
|
890
1346
|
disabled = false,
|
|
891
1347
|
acceptedFileTypes = DEFAULT_ACCEPTED_FILE_TYPES,
|
|
1348
|
+
idleAction,
|
|
892
1349
|
className,
|
|
893
1350
|
classNames,
|
|
894
1351
|
labels
|
|
@@ -896,14 +1353,15 @@ var MessageComposer = ({
|
|
|
896
1353
|
const emojiLabel = labels?.emoji ?? DEFAULT_MESSAGE_COMPOSER_LABELS.emoji;
|
|
897
1354
|
const attachLabel = labels?.attach ?? DEFAULT_MESSAGE_COMPOSER_LABELS.attach;
|
|
898
1355
|
const sendLabel = labels?.send ?? DEFAULT_MESSAGE_COMPOSER_LABELS.send;
|
|
899
|
-
const
|
|
900
|
-
const [
|
|
901
|
-
const [
|
|
902
|
-
const
|
|
903
|
-
const
|
|
1356
|
+
const removeAttachmentLabel = labels?.removeAttachment ?? DEFAULT_MESSAGE_COMPOSER_LABELS.removeAttachment;
|
|
1357
|
+
const [internalText, setInternalText] = useState8("");
|
|
1358
|
+
const [showEmoji, setShowEmoji] = useState8(false);
|
|
1359
|
+
const [attachments, setAttachments] = useState8([]);
|
|
1360
|
+
const textareaRef = useRef3(null);
|
|
1361
|
+
const fileInputRef = useRef3(null);
|
|
904
1362
|
const isControlled = externalValue !== void 0;
|
|
905
1363
|
const text = isControlled ? externalValue : internalText;
|
|
906
|
-
const setText =
|
|
1364
|
+
const setText = useCallback4((newText) => {
|
|
907
1365
|
if (isControlled) {
|
|
908
1366
|
externalOnChange?.(newText);
|
|
909
1367
|
} else {
|
|
@@ -912,7 +1370,7 @@ var MessageComposer = ({
|
|
|
912
1370
|
}, [isControlled, externalOnChange]);
|
|
913
1371
|
const showEmojiButton = features?.emoji !== false;
|
|
914
1372
|
const showAttachButton = features?.documents !== false;
|
|
915
|
-
const sendMessage =
|
|
1373
|
+
const sendMessage = useCallback4(() => {
|
|
916
1374
|
const trimmed = text.trim();
|
|
917
1375
|
if (!trimmed && attachments.length === 0) return;
|
|
918
1376
|
if (trimmed) onSend(trimmed);
|
|
@@ -925,19 +1383,19 @@ var MessageComposer = ({
|
|
|
925
1383
|
setShowEmoji(false);
|
|
926
1384
|
if (textareaRef.current) textareaRef.current.style.height = "auto";
|
|
927
1385
|
}, [text, attachments, onSend, onAttach, isControlled]);
|
|
928
|
-
const handleKeyDown =
|
|
1386
|
+
const handleKeyDown = useCallback4((e) => {
|
|
929
1387
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
930
1388
|
e.preventDefault();
|
|
931
1389
|
if (!disabled) sendMessage();
|
|
932
1390
|
}
|
|
933
1391
|
}, [sendMessage, disabled]);
|
|
934
|
-
const handleInput =
|
|
1392
|
+
const handleInput = useCallback4(() => {
|
|
935
1393
|
const ta = textareaRef.current;
|
|
936
1394
|
if (!ta) return;
|
|
937
1395
|
ta.style.height = "auto";
|
|
938
1396
|
ta.style.height = `${Math.min(ta.scrollHeight, 100)}px`;
|
|
939
1397
|
}, []);
|
|
940
|
-
const handleEmojiSelect =
|
|
1398
|
+
const handleEmojiSelect = useCallback4((emoji) => {
|
|
941
1399
|
const ta = textareaRef.current;
|
|
942
1400
|
if (!ta) {
|
|
943
1401
|
setText(text + emoji);
|
|
@@ -953,7 +1411,7 @@ var MessageComposer = ({
|
|
|
953
1411
|
handleInput();
|
|
954
1412
|
});
|
|
955
1413
|
}, [text, setText, handleInput]);
|
|
956
|
-
const handleFileChange =
|
|
1414
|
+
const handleFileChange = useCallback4((e) => {
|
|
957
1415
|
const files = e.target.files;
|
|
958
1416
|
if (!files) return;
|
|
959
1417
|
const previews = [];
|
|
@@ -964,7 +1422,7 @@ var MessageComposer = ({
|
|
|
964
1422
|
setAttachments((prev) => [...prev, ...previews]);
|
|
965
1423
|
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
966
1424
|
}, []);
|
|
967
|
-
const removeAttachment =
|
|
1425
|
+
const removeAttachment = useCallback4((index) => {
|
|
968
1426
|
setAttachments((prev) => {
|
|
969
1427
|
const next = [...prev];
|
|
970
1428
|
if (next[index].previewUrl) URL.revokeObjectURL(next[index].previewUrl);
|
|
@@ -972,7 +1430,7 @@ var MessageComposer = ({
|
|
|
972
1430
|
return next;
|
|
973
1431
|
});
|
|
974
1432
|
}, []);
|
|
975
|
-
const insertFormatting =
|
|
1433
|
+
const insertFormatting = useCallback4((marker) => {
|
|
976
1434
|
const ta = textareaRef.current;
|
|
977
1435
|
if (!ta) return;
|
|
978
1436
|
const start = ta.selectionStart;
|
|
@@ -986,31 +1444,56 @@ var MessageComposer = ({
|
|
|
986
1444
|
});
|
|
987
1445
|
}
|
|
988
1446
|
}, [text, setText]);
|
|
1447
|
+
const effectiveIdleAction = idleAction ?? (onAttach ? /* @__PURE__ */ jsx14(AudioRecorderButton, { onRecorded: (file) => onAttach(file) }) : void 0);
|
|
989
1448
|
const canSend = text.trim().length > 0 || attachments.length > 0;
|
|
990
1449
|
const remaining = maxLength ? maxLength - text.length : null;
|
|
991
1450
|
return (
|
|
992
1451
|
/* A barra é a superfície (cinza, largura cheia, sem raio) e o campo dentro é que arredonda —
|
|
993
1452
|
ordem do WhatsApp. Invertido, o pill arredondado ia até a borda da tela e os cantos
|
|
994
1453
|
descobriam o fundo branco da página, que lia como defeito. */
|
|
995
|
-
/* @__PURE__ */
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1454
|
+
/* @__PURE__ */ jsxs10("div", { className: cn(COMPOSER_BAR_CLASS, className), children: [
|
|
1455
|
+
quickReplies && quickReplies.length > 0 && /* @__PURE__ */ jsx14(
|
|
1456
|
+
"div",
|
|
1457
|
+
{
|
|
1458
|
+
className: cn(
|
|
1459
|
+
"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",
|
|
1460
|
+
classNames?.quickReplies
|
|
1461
|
+
),
|
|
1462
|
+
children: quickReplies.map((quickReply) => /* @__PURE__ */ jsx14(
|
|
1463
|
+
"button",
|
|
1464
|
+
{
|
|
1465
|
+
"data-cv-tooltip": quickReply.label,
|
|
1466
|
+
"aria-label": quickReply.label,
|
|
1467
|
+
type: "button",
|
|
1468
|
+
onClick: () => {
|
|
1469
|
+
setText(resolveQuickReply(quickReply, quickReplyVariables));
|
|
1470
|
+
textareaRef.current?.focus();
|
|
1471
|
+
},
|
|
1472
|
+
className: cn(QUICK_REPLY_PILL_CLASS, classNames?.quickReply),
|
|
1473
|
+
children: quickReply.label
|
|
1474
|
+
},
|
|
1475
|
+
quickReply.key
|
|
1476
|
+
))
|
|
1477
|
+
}
|
|
1478
|
+
),
|
|
1479
|
+
attachments.length > 0 && /* @__PURE__ */ jsx14("div", { className: "flex gap-2 px-1 pb-2 overflow-x-auto", children: attachments.map((a, i) => /* @__PURE__ */ jsxs10("div", { className: "relative flex-shrink-0", children: [
|
|
1480
|
+
a.previewUrl ? /* @__PURE__ */ jsx14("img", { src: a.previewUrl, alt: "", className: "w-16 h-16 object-cover rounded-lg border border-gray-200" }) : /* @__PURE__ */ jsx14("div", { className: "w-16 h-16 bg-gray-100 rounded-lg border border-gray-200 flex items-center justify-center", children: /* @__PURE__ */ jsxs10("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#9ca3af", strokeWidth: "1.5", children: [
|
|
1481
|
+
/* @__PURE__ */ jsx14("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
1482
|
+
/* @__PURE__ */ jsx14("polyline", { points: "14 2 14 8 20 8" })
|
|
1000
1483
|
] }) }),
|
|
1001
|
-
/* @__PURE__ */
|
|
1484
|
+
/* @__PURE__ */ jsx14("button", { "data-cv-tooltip": removeAttachmentLabel, "aria-label": removeAttachmentLabel, 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" })
|
|
1002
1485
|
] }, i)) }),
|
|
1003
|
-
/* @__PURE__ */
|
|
1004
|
-
showEmojiButton && /* @__PURE__ */
|
|
1005
|
-
/* @__PURE__ */
|
|
1006
|
-
/* @__PURE__ */
|
|
1007
|
-
/* @__PURE__ */
|
|
1008
|
-
/* @__PURE__ */
|
|
1009
|
-
/* @__PURE__ */
|
|
1486
|
+
/* @__PURE__ */ jsxs10("div", { className: cn("flex items-end gap-1.5 rounded-xl bg-white px-3 py-2", classNames?.field), children: [
|
|
1487
|
+
showEmojiButton && /* @__PURE__ */ jsxs10("div", { className: "relative flex-shrink-0", children: [
|
|
1488
|
+
/* @__PURE__ */ jsx14("button", { "data-cv-tooltip": emojiLabel, 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__ */ jsxs10("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
1489
|
+
/* @__PURE__ */ jsx14("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1490
|
+
/* @__PURE__ */ jsx14("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
|
|
1491
|
+
/* @__PURE__ */ jsx14("circle", { cx: "9", cy: "9", r: "0.5", fill: "currentColor" }),
|
|
1492
|
+
/* @__PURE__ */ jsx14("circle", { cx: "15", cy: "9", r: "0.5", fill: "currentColor" })
|
|
1010
1493
|
] }) }),
|
|
1011
|
-
showEmoji && /* @__PURE__ */
|
|
1494
|
+
showEmoji && /* @__PURE__ */ jsx14("div", { className: "absolute bottom-full left-0 mb-2 z-10", children: /* @__PURE__ */ jsx14(EmojiPicker, { onSelect: handleEmojiSelect }) })
|
|
1012
1495
|
] }),
|
|
1013
|
-
/* @__PURE__ */
|
|
1496
|
+
/* @__PURE__ */ jsx14(
|
|
1014
1497
|
"textarea",
|
|
1015
1498
|
{
|
|
1016
1499
|
ref: textareaRef,
|
|
@@ -1027,31 +1510,32 @@ var MessageComposer = ({
|
|
|
1027
1510
|
style: { fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" }
|
|
1028
1511
|
}
|
|
1029
1512
|
),
|
|
1030
|
-
showAttachButton && /* @__PURE__ */
|
|
1031
|
-
/* @__PURE__ */
|
|
1032
|
-
/* @__PURE__ */
|
|
1513
|
+
showAttachButton && /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
1514
|
+
/* @__PURE__ */ jsx14("input", { ref: fileInputRef, type: "file", multiple: true, accept: acceptedFileTypes, onChange: handleFileChange, className: "hidden" }),
|
|
1515
|
+
/* @__PURE__ */ jsx14("button", { "data-cv-tooltip": attachLabel, 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__ */ jsx14("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx14("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" }) }) })
|
|
1033
1516
|
] }),
|
|
1034
|
-
/* @__PURE__ */
|
|
1517
|
+
!canSend && effectiveIdleAction ? /* @__PURE__ */ jsx14("div", { className: "flex-shrink-0", children: effectiveIdleAction }) : /* @__PURE__ */ jsx14(
|
|
1035
1518
|
"button",
|
|
1036
1519
|
{
|
|
1520
|
+
"data-cv-tooltip": sendLabel,
|
|
1037
1521
|
onClick: sendMessage,
|
|
1038
1522
|
disabled: !canSend || disabled,
|
|
1039
1523
|
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"}`,
|
|
1040
1524
|
"aria-label": sendLabel,
|
|
1041
|
-
children: /* @__PURE__ */
|
|
1525
|
+
children: /* @__PURE__ */ jsx14("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx14("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
|
|
1042
1526
|
}
|
|
1043
1527
|
)
|
|
1044
1528
|
] }),
|
|
1045
|
-
remaining !== null && /* @__PURE__ */
|
|
1529
|
+
remaining !== null && /* @__PURE__ */ jsx14("div", { className: "flex justify-end mt-1 pr-1", children: /* @__PURE__ */ jsx14("span", { className: `text-xs ${remaining < 20 ? "text-red-500" : "text-gray-400"}`, children: remaining }) })
|
|
1046
1530
|
] })
|
|
1047
1531
|
);
|
|
1048
1532
|
};
|
|
1049
1533
|
|
|
1050
1534
|
// src/DateDivider.tsx
|
|
1051
|
-
import { jsx as
|
|
1535
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1052
1536
|
function DateDivider({ iso, className, classNames }) {
|
|
1053
1537
|
const { dateDivider } = useConversationLocales();
|
|
1054
|
-
return /* @__PURE__ */
|
|
1538
|
+
return /* @__PURE__ */ jsx15("div", { className: cn("flex justify-center sticky top-0 z-10 my-2 pointer-events-none", classNames?.root, className), children: /* @__PURE__ */ jsx15(
|
|
1055
1539
|
"span",
|
|
1056
1540
|
{
|
|
1057
1541
|
className: cn(
|
|
@@ -1117,14 +1601,93 @@ function phoneInitials(number) {
|
|
|
1117
1601
|
return digits.slice(-2);
|
|
1118
1602
|
}
|
|
1119
1603
|
|
|
1604
|
+
// src/conversationChannel.ts
|
|
1605
|
+
var CONVERSATION_CHANNEL = {
|
|
1606
|
+
WHATSAPP: "whatsapp",
|
|
1607
|
+
MESSENGER: "messenger",
|
|
1608
|
+
INSTAGRAM: "instagram",
|
|
1609
|
+
WEBCHAT: "webchat"
|
|
1610
|
+
};
|
|
1611
|
+
var DEFAULT_CONVERSATION_CHANNEL = CONVERSATION_CHANNEL.WHATSAPP;
|
|
1612
|
+
var REOPEN_MECHANISM = {
|
|
1613
|
+
TEMPLATE: "template",
|
|
1614
|
+
TAG: "tag",
|
|
1615
|
+
NONE: "none"
|
|
1616
|
+
};
|
|
1617
|
+
var HANDLE_KIND = {
|
|
1618
|
+
PHONE: "phone",
|
|
1619
|
+
USERNAME: "username",
|
|
1620
|
+
SESSION: "session"
|
|
1621
|
+
};
|
|
1622
|
+
var CHANNEL_CAPABILITIES = {
|
|
1623
|
+
[CONVERSATION_CHANNEL.WHATSAPP]: {
|
|
1624
|
+
label: "WhatsApp",
|
|
1625
|
+
icon: "\u{1F4AC}",
|
|
1626
|
+
hasSessionWindow: true,
|
|
1627
|
+
windowHours: 24,
|
|
1628
|
+
reopenMechanism: REOPEN_MECHANISM.TEMPLATE,
|
|
1629
|
+
handleKind: HANDLE_KIND.PHONE
|
|
1630
|
+
},
|
|
1631
|
+
[CONVERSATION_CHANNEL.MESSENGER]: {
|
|
1632
|
+
// Messenger também tem 24h, mas reabre com message tag — não com template aprovado.
|
|
1633
|
+
label: "Messenger",
|
|
1634
|
+
icon: "\u{1F4E8}",
|
|
1635
|
+
hasSessionWindow: true,
|
|
1636
|
+
windowHours: 24,
|
|
1637
|
+
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
1638
|
+
handleKind: HANDLE_KIND.USERNAME
|
|
1639
|
+
},
|
|
1640
|
+
[CONVERSATION_CHANNEL.INSTAGRAM]: {
|
|
1641
|
+
label: "Instagram",
|
|
1642
|
+
icon: "\u{1F4F7}",
|
|
1643
|
+
hasSessionWindow: true,
|
|
1644
|
+
windowHours: 24,
|
|
1645
|
+
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
1646
|
+
handleKind: HANDLE_KIND.USERNAME
|
|
1647
|
+
},
|
|
1648
|
+
[CONVERSATION_CHANNEL.WEBCHAT]: {
|
|
1649
|
+
// Chat próprio: sem intermediário, sem janela. Bloquear o composer aqui seria inventar limite.
|
|
1650
|
+
label: "Chat do site",
|
|
1651
|
+
icon: "\u{1F310}",
|
|
1652
|
+
hasSessionWindow: false,
|
|
1653
|
+
windowHours: 0,
|
|
1654
|
+
reopenMechanism: REOPEN_MECHANISM.NONE,
|
|
1655
|
+
handleKind: HANDLE_KIND.SESSION
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
function capabilitiesOf(channel) {
|
|
1659
|
+
return CHANNEL_CAPABILITIES[channel ?? DEFAULT_CONVERSATION_CHANNEL];
|
|
1660
|
+
}
|
|
1661
|
+
var CHANNEL_FILTER_ALL = "all";
|
|
1662
|
+
function channelFiltersFor(conversations) {
|
|
1663
|
+
const present = new Set(
|
|
1664
|
+
conversations.map((conversation) => conversation.channel ?? DEFAULT_CONVERSATION_CHANNEL)
|
|
1665
|
+
);
|
|
1666
|
+
if (present.size < 2) return [];
|
|
1667
|
+
const ordered = Object.keys(CHANNEL_CAPABILITIES).filter((channel) => present.has(channel));
|
|
1668
|
+
return [
|
|
1669
|
+
{ value: CHANNEL_FILTER_ALL, label: "Todos" },
|
|
1670
|
+
...ordered.map((channel) => ({ value: channel, label: CHANNEL_CAPABILITIES[channel].label }))
|
|
1671
|
+
];
|
|
1672
|
+
}
|
|
1673
|
+
function formatContactHandle(params) {
|
|
1674
|
+
const { handleKind } = capabilitiesOf(params.channel);
|
|
1675
|
+
if (handleKind === HANDLE_KIND.PHONE) return formatPhone(params.handle);
|
|
1676
|
+
if (handleKind === HANDLE_KIND.USERNAME) return params.handle.startsWith("@") ? params.handle : `@${params.handle}`;
|
|
1677
|
+
return `Visitante ${params.handle.slice(-6)}`;
|
|
1678
|
+
}
|
|
1679
|
+
function contactFlag(params) {
|
|
1680
|
+
return capabilitiesOf(params.channel).handleKind === HANDLE_KIND.PHONE ? phoneCountryFlag(params.handle) : "";
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1120
1683
|
// src/hooks/useAsyncResource.ts
|
|
1121
|
-
import { useCallback as
|
|
1684
|
+
import { useCallback as useCallback5, useEffect as useEffect3, useRef as useRef4, useState as useState9 } from "react";
|
|
1122
1685
|
function useAsyncResource(fetcher, deps) {
|
|
1123
|
-
const [data, setData] =
|
|
1124
|
-
const [loading, setLoading] =
|
|
1125
|
-
const [error, setError] =
|
|
1126
|
-
const requestIdRef =
|
|
1127
|
-
const load =
|
|
1686
|
+
const [data, setData] = useState9(void 0);
|
|
1687
|
+
const [loading, setLoading] = useState9(false);
|
|
1688
|
+
const [error, setError] = useState9(void 0);
|
|
1689
|
+
const requestIdRef = useRef4(0);
|
|
1690
|
+
const load = useCallback5(async () => {
|
|
1128
1691
|
const requestId = ++requestIdRef.current;
|
|
1129
1692
|
setLoading(true);
|
|
1130
1693
|
setError(void 0);
|
|
@@ -1137,7 +1700,7 @@ function useAsyncResource(fetcher, deps) {
|
|
|
1137
1700
|
if (requestId === requestIdRef.current) setLoading(false);
|
|
1138
1701
|
}
|
|
1139
1702
|
}, deps);
|
|
1140
|
-
|
|
1703
|
+
useEffect3(() => {
|
|
1141
1704
|
load();
|
|
1142
1705
|
}, [load]);
|
|
1143
1706
|
return { data, loading, error, refetch: load };
|
|
@@ -1172,9 +1735,19 @@ function useConversationDocuments(conversationId, params) {
|
|
|
1172
1735
|
}
|
|
1173
1736
|
|
|
1174
1737
|
// src/ConversationDocumentsPanel.tsx
|
|
1175
|
-
import { useState as
|
|
1738
|
+
import { useState as useState10 } from "react";
|
|
1176
1739
|
import { ArrowUpDown, Bot, Download, Eye, Users } from "lucide-react";
|
|
1177
|
-
|
|
1740
|
+
|
|
1741
|
+
// src/pagination.constant.ts
|
|
1742
|
+
var PAGINATION_LABELS = {
|
|
1743
|
+
first: "Primeira p\xE1gina",
|
|
1744
|
+
previous: "P\xE1gina anterior",
|
|
1745
|
+
next: "Pr\xF3xima p\xE1gina",
|
|
1746
|
+
last: "\xDAltima p\xE1gina"
|
|
1747
|
+
};
|
|
1748
|
+
|
|
1749
|
+
// src/ConversationDocumentsPanel.tsx
|
|
1750
|
+
import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1178
1751
|
var DOCUMENT_SOURCE_FILTER = {
|
|
1179
1752
|
ALL: "all",
|
|
1180
1753
|
CUSTOMER: "customer",
|
|
@@ -1214,12 +1787,12 @@ function ConversationDocumentsPanel({
|
|
|
1214
1787
|
}) {
|
|
1215
1788
|
const labels = { ...DEFAULT_CONVERSATION_DOCUMENTS_LABELS, ...labelsOverride };
|
|
1216
1789
|
const context = useConversations();
|
|
1217
|
-
const [search, setSearch] =
|
|
1218
|
-
const [sourceFilter, setSourceFilter] =
|
|
1219
|
-
const [sortDirection, setSortDirection] =
|
|
1220
|
-
const [page, setPage] =
|
|
1221
|
-
const [selectedIds, setSelectedIds] =
|
|
1222
|
-
const [archiveError, setArchiveError] =
|
|
1790
|
+
const [search, setSearch] = useState10("");
|
|
1791
|
+
const [sourceFilter, setSourceFilter] = useState10(DOCUMENT_SOURCE_FILTER.ALL);
|
|
1792
|
+
const [sortDirection, setSortDirection] = useState10("desc");
|
|
1793
|
+
const [page, setPage] = useState10(1);
|
|
1794
|
+
const [selectedIds, setSelectedIds] = useState10([]);
|
|
1795
|
+
const [archiveError, setArchiveError] = useState10(false);
|
|
1223
1796
|
const hasFilters = search !== "" || sourceFilter !== DOCUMENT_SOURCE_FILTER.ALL || sortDirection !== "desc";
|
|
1224
1797
|
const { documents, total, loading, error } = useConversationDocuments(open ? conversationId : void 0, {
|
|
1225
1798
|
search,
|
|
@@ -1270,10 +1843,10 @@ function ConversationDocumentsPanel({
|
|
|
1270
1843
|
}
|
|
1271
1844
|
}
|
|
1272
1845
|
if (!open) return null;
|
|
1273
|
-
return /* @__PURE__ */
|
|
1274
|
-
/* @__PURE__ */
|
|
1275
|
-
/* @__PURE__ */
|
|
1276
|
-
/* @__PURE__ */
|
|
1846
|
+
return /* @__PURE__ */ jsx16("div", { className: cn("border-b", classNames?.root, className), children: /* @__PURE__ */ jsxs11("section", { className: cn("px-4 py-3", classNames?.body), children: [
|
|
1847
|
+
/* @__PURE__ */ jsx16("p", { className: cn("mb-2 text-sm font-medium", classNames?.title), children: labels.title }),
|
|
1848
|
+
/* @__PURE__ */ jsxs11("div", { className: cn("mb-2 flex flex-wrap items-center gap-2 border-b pb-2", classNames?.filters), children: [
|
|
1849
|
+
/* @__PURE__ */ jsx16(
|
|
1277
1850
|
"input",
|
|
1278
1851
|
{
|
|
1279
1852
|
type: "search",
|
|
@@ -1284,7 +1857,7 @@ function ConversationDocumentsPanel({
|
|
|
1284
1857
|
className: cn("w-full rounded-md border px-3 py-2 text-sm sm:w-52", classNames?.search)
|
|
1285
1858
|
}
|
|
1286
1859
|
),
|
|
1287
|
-
/* @__PURE__ */
|
|
1860
|
+
/* @__PURE__ */ jsxs11(
|
|
1288
1861
|
"select",
|
|
1289
1862
|
{
|
|
1290
1863
|
value: sourceFilter,
|
|
@@ -1292,27 +1865,31 @@ function ConversationDocumentsPanel({
|
|
|
1292
1865
|
"aria-label": labels.sourceFilterAll,
|
|
1293
1866
|
className: cn("w-full rounded-md border px-2 py-2 text-sm sm:w-36", classNames?.sourceSelect),
|
|
1294
1867
|
children: [
|
|
1295
|
-
/* @__PURE__ */
|
|
1296
|
-
/* @__PURE__ */
|
|
1297
|
-
/* @__PURE__ */
|
|
1868
|
+
/* @__PURE__ */ jsx16("option", { value: DOCUMENT_SOURCE_FILTER.ALL, children: labels.sourceFilterAll }),
|
|
1869
|
+
/* @__PURE__ */ jsx16("option", { value: DOCUMENT_SOURCE_FILTER.CUSTOMER, children: labels.sourceFilterCustomer }),
|
|
1870
|
+
/* @__PURE__ */ jsx16("option", { value: DOCUMENT_SOURCE_FILTER.TEAM, children: labels.sourceFilterTeam })
|
|
1298
1871
|
]
|
|
1299
1872
|
}
|
|
1300
1873
|
),
|
|
1301
|
-
/* @__PURE__ */
|
|
1874
|
+
/* @__PURE__ */ jsxs11(
|
|
1302
1875
|
"button",
|
|
1303
1876
|
{
|
|
1877
|
+
"data-cv-tooltip": sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest,
|
|
1878
|
+
"aria-label": sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest,
|
|
1304
1879
|
type: "button",
|
|
1305
1880
|
onClick: () => applyFilter(() => setSortDirection(sortDirection === "desc" ? "asc" : "desc")),
|
|
1306
1881
|
className: cn("cv-header-action inline-flex items-center gap-1", classNames?.sortButton),
|
|
1307
1882
|
children: [
|
|
1308
|
-
/* @__PURE__ */
|
|
1883
|
+
/* @__PURE__ */ jsx16(ArrowUpDown, { size: 14 }),
|
|
1309
1884
|
sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest
|
|
1310
1885
|
]
|
|
1311
1886
|
}
|
|
1312
1887
|
),
|
|
1313
|
-
hasFilters ? /* @__PURE__ */
|
|
1888
|
+
hasFilters ? /* @__PURE__ */ jsx16(
|
|
1314
1889
|
"button",
|
|
1315
1890
|
{
|
|
1891
|
+
"data-cv-tooltip": labels.clearFilters,
|
|
1892
|
+
"aria-label": labels.clearFilters,
|
|
1316
1893
|
type: "button",
|
|
1317
1894
|
onClick: () => applyFilter(() => {
|
|
1318
1895
|
setSearch("");
|
|
@@ -1324,12 +1901,12 @@ function ConversationDocumentsPanel({
|
|
|
1324
1901
|
}
|
|
1325
1902
|
) : null
|
|
1326
1903
|
] }),
|
|
1327
|
-
loading ? /* @__PURE__ */
|
|
1328
|
-
error ? /* @__PURE__ */
|
|
1329
|
-
!loading && !error && documents.length === 0 ? /* @__PURE__ */
|
|
1330
|
-
canArchive && documents.length > 0 ? /* @__PURE__ */
|
|
1331
|
-
/* @__PURE__ */
|
|
1332
|
-
/* @__PURE__ */
|
|
1904
|
+
loading ? /* @__PURE__ */ jsx16("p", { className: cn("text-xs text-gray-500", classNames?.status), children: labels.loading }) : null,
|
|
1905
|
+
error ? /* @__PURE__ */ jsx16("p", { role: "alert", className: cn("text-xs text-red-600 dark:text-red-400", classNames?.status), children: labels.failure }) : null,
|
|
1906
|
+
!loading && !error && documents.length === 0 ? /* @__PURE__ */ jsx16("p", { className: cn("text-xs text-gray-500", classNames?.status), children: hasFilters ? labels.noResults : labels.empty }) : null,
|
|
1907
|
+
canArchive && documents.length > 0 ? /* @__PURE__ */ jsxs11("div", { className: cn("mb-2 flex flex-wrap items-center gap-3 text-xs", classNames?.selectionBar), children: [
|
|
1908
|
+
/* @__PURE__ */ jsxs11("label", { className: "inline-flex items-center gap-1.5", children: [
|
|
1909
|
+
/* @__PURE__ */ jsx16(
|
|
1333
1910
|
"input",
|
|
1334
1911
|
{
|
|
1335
1912
|
type: "checkbox",
|
|
@@ -1340,13 +1917,13 @@ function ConversationDocumentsPanel({
|
|
|
1340
1917
|
),
|
|
1341
1918
|
labels.selectAll
|
|
1342
1919
|
] }),
|
|
1343
|
-
selectedIds.length > 0 ? /* @__PURE__ */
|
|
1344
|
-
archiveError ? /* @__PURE__ */
|
|
1920
|
+
selectedIds.length > 0 ? /* @__PURE__ */ jsx16("button", { "data-cv-tooltip": labels.downloadSelected(selectedIds.length), "aria-label": labels.downloadSelected(selectedIds.length), type: "button", onClick: () => void handleDownloadSelected(), className: "cv-header-action", children: labels.downloadSelected(selectedIds.length) }) : null,
|
|
1921
|
+
archiveError ? /* @__PURE__ */ jsx16("span", { role: "alert", className: "text-red-600 dark:text-red-400", children: labels.archiveFailed }) : null
|
|
1345
1922
|
] }) : null,
|
|
1346
|
-
/* @__PURE__ */
|
|
1923
|
+
/* @__PURE__ */ jsx16("ul", { className: cn("space-y-2", classNames?.list), children: documents.map((document2) => {
|
|
1347
1924
|
const isFromCustomer = !TEAM_SOURCES.has(document2.source);
|
|
1348
1925
|
const SourceIcon = isFromCustomer ? Users : Bot;
|
|
1349
|
-
return /* @__PURE__ */
|
|
1926
|
+
return /* @__PURE__ */ jsxs11(
|
|
1350
1927
|
"li",
|
|
1351
1928
|
{
|
|
1352
1929
|
className: cn(
|
|
@@ -1354,8 +1931,8 @@ function ConversationDocumentsPanel({
|
|
|
1354
1931
|
classNames?.item
|
|
1355
1932
|
),
|
|
1356
1933
|
children: [
|
|
1357
|
-
/* @__PURE__ */
|
|
1358
|
-
canArchive ? /* @__PURE__ */
|
|
1934
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
|
|
1935
|
+
canArchive ? /* @__PURE__ */ jsx16(
|
|
1359
1936
|
"input",
|
|
1360
1937
|
{
|
|
1361
1938
|
type: "checkbox",
|
|
@@ -1365,9 +1942,9 @@ function ConversationDocumentsPanel({
|
|
|
1365
1942
|
className: cn("shrink-0", classNames?.checkbox)
|
|
1366
1943
|
}
|
|
1367
1944
|
) : null,
|
|
1368
|
-
/* @__PURE__ */
|
|
1369
|
-
/* @__PURE__ */
|
|
1370
|
-
/* @__PURE__ */
|
|
1945
|
+
/* @__PURE__ */ jsx16(FileIcon, { filename: document2.filename, mimeType: document2.mimeType }),
|
|
1946
|
+
/* @__PURE__ */ jsxs11("div", { className: "min-w-0 flex-1", children: [
|
|
1947
|
+
/* @__PURE__ */ jsxs11(
|
|
1371
1948
|
"div",
|
|
1372
1949
|
{
|
|
1373
1950
|
className: cn(
|
|
@@ -1376,47 +1953,47 @@ function ConversationDocumentsPanel({
|
|
|
1376
1953
|
classNames?.sourceBadge
|
|
1377
1954
|
),
|
|
1378
1955
|
children: [
|
|
1379
|
-
/* @__PURE__ */
|
|
1956
|
+
/* @__PURE__ */ jsx16(SourceIcon, { size: 11 }),
|
|
1380
1957
|
isFromCustomer ? labels.sourceFilterCustomer : labels.sourceFilterTeam
|
|
1381
1958
|
]
|
|
1382
1959
|
}
|
|
1383
1960
|
),
|
|
1384
|
-
/* @__PURE__ */
|
|
1961
|
+
/* @__PURE__ */ jsx16(
|
|
1385
1962
|
"div",
|
|
1386
1963
|
{
|
|
1387
1964
|
className: cn("truncate text-sm font-medium", classNames?.filename),
|
|
1388
|
-
|
|
1965
|
+
"data-cv-tooltip": document2.filename,
|
|
1389
1966
|
children: document2.filename
|
|
1390
1967
|
}
|
|
1391
1968
|
),
|
|
1392
|
-
/* @__PURE__ */
|
|
1969
|
+
/* @__PURE__ */ jsxs11("div", { className: cn("text-xs text-gray-500 dark:text-gray-400", classNames?.meta), children: [
|
|
1393
1970
|
formatDateTime(document2.linkedAt),
|
|
1394
1971
|
" \xB7 ",
|
|
1395
1972
|
formatFileSize(document2.sizeBytes)
|
|
1396
1973
|
] })
|
|
1397
1974
|
] })
|
|
1398
1975
|
] }),
|
|
1399
|
-
/* @__PURE__ */
|
|
1400
|
-
/* @__PURE__ */
|
|
1976
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex shrink-0 gap-1", children: [
|
|
1977
|
+
/* @__PURE__ */ jsx16(
|
|
1401
1978
|
"button",
|
|
1402
1979
|
{
|
|
1403
1980
|
type: "button",
|
|
1404
1981
|
onClick: () => void handleOpen(document2.id, "inline"),
|
|
1405
|
-
|
|
1982
|
+
"data-cv-tooltip": labels.view,
|
|
1406
1983
|
"aria-label": `${labels.view}: ${document2.filename}`,
|
|
1407
1984
|
className: cn("cv-header-icon", classNames?.viewButton),
|
|
1408
|
-
children: /* @__PURE__ */
|
|
1985
|
+
children: /* @__PURE__ */ jsx16(Eye, { size: 14 })
|
|
1409
1986
|
}
|
|
1410
1987
|
),
|
|
1411
|
-
/* @__PURE__ */
|
|
1988
|
+
/* @__PURE__ */ jsx16(
|
|
1412
1989
|
"button",
|
|
1413
1990
|
{
|
|
1414
1991
|
type: "button",
|
|
1415
1992
|
onClick: () => void handleOpen(document2.id, "attachment"),
|
|
1416
|
-
|
|
1993
|
+
"data-cv-tooltip": labels.download,
|
|
1417
1994
|
"aria-label": `${labels.download}: ${document2.filename}`,
|
|
1418
1995
|
className: cn("cv-header-icon", classNames?.downloadButton),
|
|
1419
|
-
children: /* @__PURE__ */
|
|
1996
|
+
children: /* @__PURE__ */ jsx16(Download, { size: 14 })
|
|
1420
1997
|
}
|
|
1421
1998
|
)
|
|
1422
1999
|
] })
|
|
@@ -1425,7 +2002,7 @@ function ConversationDocumentsPanel({
|
|
|
1425
2002
|
document2.id
|
|
1426
2003
|
);
|
|
1427
2004
|
}) }),
|
|
1428
|
-
total > perPage ? /* @__PURE__ */
|
|
2005
|
+
total > perPage ? /* @__PURE__ */ jsxs11(
|
|
1429
2006
|
"div",
|
|
1430
2007
|
{
|
|
1431
2008
|
className: cn(
|
|
@@ -1433,27 +2010,29 @@ function ConversationDocumentsPanel({
|
|
|
1433
2010
|
classNames?.pagination
|
|
1434
2011
|
),
|
|
1435
2012
|
children: [
|
|
1436
|
-
/* @__PURE__ */
|
|
1437
|
-
/* @__PURE__ */
|
|
1438
|
-
/* @__PURE__ */
|
|
2013
|
+
/* @__PURE__ */ jsx16("span", { className: "text-gray-400", children: labels.total(total) }),
|
|
2014
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
|
|
2015
|
+
/* @__PURE__ */ jsx16(
|
|
1439
2016
|
"button",
|
|
1440
2017
|
{
|
|
1441
2018
|
type: "button",
|
|
1442
2019
|
onClick: () => setPage(page - 1),
|
|
1443
2020
|
disabled: page <= 1,
|
|
1444
|
-
"aria-label":
|
|
2021
|
+
"aria-label": PAGINATION_LABELS.previous,
|
|
2022
|
+
"data-cv-tooltip": PAGINATION_LABELS.previous,
|
|
1445
2023
|
className: "cv-header-icon disabled:opacity-40",
|
|
1446
2024
|
children: "\u2039"
|
|
1447
2025
|
}
|
|
1448
2026
|
),
|
|
1449
|
-
/* @__PURE__ */
|
|
1450
|
-
/* @__PURE__ */
|
|
2027
|
+
/* @__PURE__ */ jsx16("span", { className: "text-gray-500", children: labels.page(page, lastPage) }),
|
|
2028
|
+
/* @__PURE__ */ jsx16(
|
|
1451
2029
|
"button",
|
|
1452
2030
|
{
|
|
1453
2031
|
type: "button",
|
|
1454
2032
|
onClick: () => setPage(page + 1),
|
|
1455
2033
|
disabled: page >= lastPage,
|
|
1456
|
-
"aria-label":
|
|
2034
|
+
"aria-label": PAGINATION_LABELS.next,
|
|
2035
|
+
"data-cv-tooltip": PAGINATION_LABELS.next,
|
|
1457
2036
|
className: "cv-header-icon disabled:opacity-40",
|
|
1458
2037
|
children: "\u203A"
|
|
1459
2038
|
}
|
|
@@ -1466,9 +2045,9 @@ function ConversationDocumentsPanel({
|
|
|
1466
2045
|
}
|
|
1467
2046
|
|
|
1468
2047
|
// src/DocumentsLibrary.tsx
|
|
1469
|
-
import { useEffect as
|
|
1470
|
-
import { ArrowUpDown as ArrowUpDown2, Bot as Bot2, Download as Download2, Eye as Eye2, MessageSquare, Users as Users2 } from "lucide-react";
|
|
1471
|
-
import { jsx as
|
|
2048
|
+
import { useEffect as useEffect4, useRef as useRef5, useState as useState11 } from "react";
|
|
2049
|
+
import { ArrowUpDown as ArrowUpDown2, Bot as Bot2, Download as Download2, Eye as Eye2, MessageSquare, Upload, Users as Users2 } from "lucide-react";
|
|
2050
|
+
import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1472
2051
|
var DEFAULT_DOCUMENTS_LIBRARY_LABELS = {
|
|
1473
2052
|
title: "Documentos",
|
|
1474
2053
|
searchPlaceholder: "Buscar por nome do arquivo ou telefone",
|
|
@@ -1485,6 +2064,8 @@ var DEFAULT_DOCUMENTS_LIBRARY_LABELS = {
|
|
|
1485
2064
|
sortMostRecent: "Mais recentes",
|
|
1486
2065
|
sortOldest: "Mais antigos",
|
|
1487
2066
|
clearFilters: "Limpar filtros",
|
|
2067
|
+
upload: "Enviar documento",
|
|
2068
|
+
uploadError: "N\xE3o foi poss\xEDvel enviar o arquivo.",
|
|
1488
2069
|
total: (count) => `${count} arquivo${count === 1 ? "" : "s"}`,
|
|
1489
2070
|
page: (current, last) => `${current} / ${last}`
|
|
1490
2071
|
};
|
|
@@ -1499,18 +2080,23 @@ function DocumentsLibrary({
|
|
|
1499
2080
|
}) {
|
|
1500
2081
|
const labels = { ...DEFAULT_DOCUMENTS_LIBRARY_LABELS, ...labelsOverride };
|
|
1501
2082
|
const context = useConversations();
|
|
1502
|
-
const [search, setSearch] =
|
|
1503
|
-
const [sourceFilter, setSourceFilter] =
|
|
1504
|
-
const [sortDirection, setSortDirection] =
|
|
1505
|
-
const [page, setPage] =
|
|
1506
|
-
const [documents, setDocuments] =
|
|
1507
|
-
const [total, setTotal] =
|
|
1508
|
-
const [loading, setLoading] =
|
|
1509
|
-
const [failed, setFailed] =
|
|
2083
|
+
const [search, setSearch] = useState11("");
|
|
2084
|
+
const [sourceFilter, setSourceFilter] = useState11(DOCUMENT_SOURCE_FILTER.ALL);
|
|
2085
|
+
const [sortDirection, setSortDirection] = useState11("desc");
|
|
2086
|
+
const [page, setPage] = useState11(1);
|
|
2087
|
+
const [documents, setDocuments] = useState11([]);
|
|
2088
|
+
const [total, setTotal] = useState11(0);
|
|
2089
|
+
const [loading, setLoading] = useState11(false);
|
|
2090
|
+
const [failed, setFailed] = useState11(false);
|
|
2091
|
+
const [uploading, setUploading] = useState11(false);
|
|
2092
|
+
const [uploadFailed, setUploadFailed] = useState11(false);
|
|
2093
|
+
const [reloadToken, setReloadToken] = useState11(0);
|
|
2094
|
+
const fileInputRef = useRef5(null);
|
|
1510
2095
|
const hasFilters = search !== "" || sourceFilter !== DOCUMENT_SOURCE_FILTER.ALL || sortDirection !== "desc";
|
|
1511
2096
|
const lastPage = Math.max(1, Math.ceil(total / perPage));
|
|
1512
2097
|
const fetchAll = context?.api.getAllDocuments;
|
|
1513
|
-
|
|
2098
|
+
const uploadDocument = context?.api.uploadDocument;
|
|
2099
|
+
useEffect4(() => {
|
|
1514
2100
|
if (!fetchAll) return;
|
|
1515
2101
|
let active = true;
|
|
1516
2102
|
setLoading(true);
|
|
@@ -1533,7 +2119,7 @@ function DocumentsLibrary({
|
|
|
1533
2119
|
return () => {
|
|
1534
2120
|
active = false;
|
|
1535
2121
|
};
|
|
1536
|
-
}, [fetchAll, search, sourceFilter, sortDirection, page, perPage]);
|
|
2122
|
+
}, [fetchAll, search, sourceFilter, sortDirection, page, perPage, reloadToken]);
|
|
1537
2123
|
function applyFilter(change) {
|
|
1538
2124
|
change();
|
|
1539
2125
|
setPage(1);
|
|
@@ -1542,11 +2128,24 @@ function DocumentsLibrary({
|
|
|
1542
2128
|
const url = await context?.api.getDocumentUrl(uploadId, disposition);
|
|
1543
2129
|
if (url) window.open(url, "_blank", "noopener,noreferrer");
|
|
1544
2130
|
}
|
|
2131
|
+
async function handleUpload(file) {
|
|
2132
|
+
if (!uploadDocument) return;
|
|
2133
|
+
setUploading(true);
|
|
2134
|
+
setUploadFailed(false);
|
|
2135
|
+
try {
|
|
2136
|
+
await uploadDocument(file);
|
|
2137
|
+
setReloadToken((token) => token + 1);
|
|
2138
|
+
} catch {
|
|
2139
|
+
setUploadFailed(true);
|
|
2140
|
+
} finally {
|
|
2141
|
+
setUploading(false);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
1545
2144
|
if (!fetchAll) return null;
|
|
1546
|
-
return /* @__PURE__ */
|
|
1547
|
-
/* @__PURE__ */
|
|
1548
|
-
/* @__PURE__ */
|
|
1549
|
-
/* @__PURE__ */
|
|
2145
|
+
return /* @__PURE__ */ jsxs12("div", { className: cn("space-y-3", classNames?.root, className), children: [
|
|
2146
|
+
/* @__PURE__ */ jsx17("h2", { className: cn("text-lg font-semibold", classNames?.title), children: labels.title }),
|
|
2147
|
+
/* @__PURE__ */ jsxs12("div", { className: cn("flex flex-wrap items-center gap-2", classNames?.filters), children: [
|
|
2148
|
+
/* @__PURE__ */ jsx17(
|
|
1550
2149
|
"input",
|
|
1551
2150
|
{
|
|
1552
2151
|
type: "search",
|
|
@@ -1557,7 +2156,7 @@ function DocumentsLibrary({
|
|
|
1557
2156
|
className: cn("w-full rounded-md border px-3 py-2 text-sm sm:w-64", classNames?.search)
|
|
1558
2157
|
}
|
|
1559
2158
|
),
|
|
1560
|
-
/* @__PURE__ */
|
|
2159
|
+
/* @__PURE__ */ jsxs12(
|
|
1561
2160
|
"select",
|
|
1562
2161
|
{
|
|
1563
2162
|
value: sourceFilter,
|
|
@@ -1565,27 +2164,31 @@ function DocumentsLibrary({
|
|
|
1565
2164
|
"aria-label": labels.sourceFilterAll,
|
|
1566
2165
|
className: cn("w-full rounded-md border px-2 py-2 text-sm sm:w-40", classNames?.sourceSelect),
|
|
1567
2166
|
children: [
|
|
1568
|
-
/* @__PURE__ */
|
|
1569
|
-
/* @__PURE__ */
|
|
1570
|
-
/* @__PURE__ */
|
|
2167
|
+
/* @__PURE__ */ jsx17("option", { value: DOCUMENT_SOURCE_FILTER.ALL, children: labels.sourceFilterAll }),
|
|
2168
|
+
/* @__PURE__ */ jsx17("option", { value: DOCUMENT_SOURCE_FILTER.CUSTOMER, children: labels.sourceFilterCustomer }),
|
|
2169
|
+
/* @__PURE__ */ jsx17("option", { value: DOCUMENT_SOURCE_FILTER.TEAM, children: labels.sourceFilterTeam })
|
|
1571
2170
|
]
|
|
1572
2171
|
}
|
|
1573
2172
|
),
|
|
1574
|
-
/* @__PURE__ */
|
|
2173
|
+
/* @__PURE__ */ jsxs12(
|
|
1575
2174
|
"button",
|
|
1576
2175
|
{
|
|
2176
|
+
"data-cv-tooltip": sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest,
|
|
2177
|
+
"aria-label": sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest,
|
|
1577
2178
|
type: "button",
|
|
1578
2179
|
onClick: () => applyFilter(() => setSortDirection(sortDirection === "desc" ? "asc" : "desc")),
|
|
1579
2180
|
className: cn("cv-header-action inline-flex items-center gap-1", classNames?.sortButton),
|
|
1580
2181
|
children: [
|
|
1581
|
-
/* @__PURE__ */
|
|
2182
|
+
/* @__PURE__ */ jsx17(ArrowUpDown2, { size: 14 }),
|
|
1582
2183
|
sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest
|
|
1583
2184
|
]
|
|
1584
2185
|
}
|
|
1585
2186
|
),
|
|
1586
|
-
hasFilters ? /* @__PURE__ */
|
|
2187
|
+
hasFilters ? /* @__PURE__ */ jsx17(
|
|
1587
2188
|
"button",
|
|
1588
2189
|
{
|
|
2190
|
+
"data-cv-tooltip": labels.clearFilters,
|
|
2191
|
+
"aria-label": labels.clearFilters,
|
|
1589
2192
|
type: "button",
|
|
1590
2193
|
onClick: () => applyFilter(() => {
|
|
1591
2194
|
setSearch("");
|
|
@@ -1595,15 +2198,46 @@ function DocumentsLibrary({
|
|
|
1595
2198
|
className: cn("cv-header-action", classNames?.clearButton),
|
|
1596
2199
|
children: labels.clearFilters
|
|
1597
2200
|
}
|
|
1598
|
-
) : null
|
|
2201
|
+
) : null,
|
|
2202
|
+
uploadDocument ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
2203
|
+
/* @__PURE__ */ jsx17(
|
|
2204
|
+
"input",
|
|
2205
|
+
{
|
|
2206
|
+
ref: fileInputRef,
|
|
2207
|
+
type: "file",
|
|
2208
|
+
hidden: true,
|
|
2209
|
+
onChange: (event) => {
|
|
2210
|
+
const file = event.target.files?.[0];
|
|
2211
|
+
event.target.value = "";
|
|
2212
|
+
if (file) void handleUpload(file);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
),
|
|
2216
|
+
/* @__PURE__ */ jsxs12(
|
|
2217
|
+
"button",
|
|
2218
|
+
{
|
|
2219
|
+
"data-cv-tooltip": labels.upload,
|
|
2220
|
+
"aria-label": labels.upload,
|
|
2221
|
+
type: "button",
|
|
2222
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2223
|
+
disabled: uploading,
|
|
2224
|
+
className: "cv-header-action ml-auto inline-flex items-center gap-1 disabled:opacity-40",
|
|
2225
|
+
children: [
|
|
2226
|
+
/* @__PURE__ */ jsx17(Upload, { size: 14, "aria-hidden": "true" }),
|
|
2227
|
+
labels.upload
|
|
2228
|
+
]
|
|
2229
|
+
}
|
|
2230
|
+
)
|
|
2231
|
+
] }) : null
|
|
1599
2232
|
] }),
|
|
1600
|
-
loading ? /* @__PURE__ */
|
|
1601
|
-
failed ? /* @__PURE__ */
|
|
1602
|
-
|
|
1603
|
-
/* @__PURE__ */
|
|
2233
|
+
loading ? /* @__PURE__ */ jsx17("p", { className: cn("text-sm text-gray-500", classNames?.status), children: labels.loading }) : null,
|
|
2234
|
+
failed ? /* @__PURE__ */ jsx17("p", { role: "alert", className: cn("text-sm text-red-600 dark:text-red-400", classNames?.status), children: labels.failure }) : null,
|
|
2235
|
+
uploadFailed ? /* @__PURE__ */ jsx17("p", { role: "alert", className: cn("text-sm text-red-600 dark:text-red-400", classNames?.status), children: labels.uploadError }) : null,
|
|
2236
|
+
!loading && !failed && documents.length === 0 ? /* @__PURE__ */ jsx17("p", { className: cn("text-sm text-gray-500", classNames?.status), children: hasFilters ? labels.noResults : labels.empty }) : null,
|
|
2237
|
+
/* @__PURE__ */ jsx17("ul", { className: cn("space-y-2", classNames?.list), children: documents.map((document2) => {
|
|
1604
2238
|
const isFromCustomer = !TEAM_SOURCES2.has(document2.source);
|
|
1605
2239
|
const SourceIcon = isFromCustomer ? Users2 : Bot2;
|
|
1606
|
-
return /* @__PURE__ */
|
|
2240
|
+
return /* @__PURE__ */ jsxs12(
|
|
1607
2241
|
"li",
|
|
1608
2242
|
{
|
|
1609
2243
|
className: cn(
|
|
@@ -1611,56 +2245,57 @@ function DocumentsLibrary({
|
|
|
1611
2245
|
classNames?.item
|
|
1612
2246
|
),
|
|
1613
2247
|
children: [
|
|
1614
|
-
/* @__PURE__ */
|
|
1615
|
-
/* @__PURE__ */
|
|
1616
|
-
/* @__PURE__ */
|
|
1617
|
-
/* @__PURE__ */
|
|
1618
|
-
/* @__PURE__ */
|
|
1619
|
-
/* @__PURE__ */
|
|
1620
|
-
/* @__PURE__ */
|
|
2248
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
|
|
2249
|
+
/* @__PURE__ */ jsx17(FileIcon, { filename: document2.filename, mimeType: document2.mimeType }),
|
|
2250
|
+
/* @__PURE__ */ jsxs12("div", { className: "min-w-0 flex-1", children: [
|
|
2251
|
+
/* @__PURE__ */ jsx17("div", { className: cn("truncate text-sm font-medium", classNames?.filename), "data-cv-tooltip": document2.filename, children: document2.filename }),
|
|
2252
|
+
/* @__PURE__ */ jsxs12("div", { className: cn("flex flex-wrap items-center gap-x-2 text-xs text-gray-500", classNames?.meta), children: [
|
|
2253
|
+
/* @__PURE__ */ jsxs12("span", { className: "inline-flex items-center gap-1", children: [
|
|
2254
|
+
/* @__PURE__ */ jsx17(SourceIcon, { size: 11 }),
|
|
1621
2255
|
isFromCustomer ? labels.sourceFilterCustomer : labels.sourceFilterTeam
|
|
1622
2256
|
] }),
|
|
1623
|
-
/* @__PURE__ */
|
|
1624
|
-
/* @__PURE__ */
|
|
1625
|
-
/* @__PURE__ */
|
|
1626
|
-
/* @__PURE__ */
|
|
2257
|
+
/* @__PURE__ */ jsx17("span", { children: "\xB7" }),
|
|
2258
|
+
/* @__PURE__ */ jsx17("span", { children: formatDateTime(document2.linkedAt) }),
|
|
2259
|
+
/* @__PURE__ */ jsx17("span", { children: "\xB7" }),
|
|
2260
|
+
/* @__PURE__ */ jsx17("span", { children: formatFileSize(document2.sizeBytes) })
|
|
1627
2261
|
] })
|
|
1628
2262
|
] })
|
|
1629
2263
|
] }),
|
|
1630
|
-
onOpenConversation ? /* @__PURE__ */
|
|
2264
|
+
onOpenConversation ? /* @__PURE__ */ jsxs12(
|
|
1631
2265
|
"button",
|
|
1632
2266
|
{
|
|
1633
2267
|
type: "button",
|
|
1634
2268
|
onClick: () => onOpenConversation(document2.conversationId),
|
|
1635
|
-
|
|
2269
|
+
"data-cv-tooltip": labels.openConversation,
|
|
2270
|
+
"aria-label": labels.openConversation,
|
|
1636
2271
|
className: cn("cv-header-action inline-flex shrink-0 items-center gap-1", classNames?.conversationLink),
|
|
1637
2272
|
children: [
|
|
1638
|
-
/* @__PURE__ */
|
|
2273
|
+
/* @__PURE__ */ jsx17(MessageSquare, { size: 12 }),
|
|
1639
2274
|
formatPhone(document2.conversationId)
|
|
1640
2275
|
]
|
|
1641
2276
|
}
|
|
1642
|
-
) : /* @__PURE__ */
|
|
1643
|
-
/* @__PURE__ */
|
|
1644
|
-
/* @__PURE__ */
|
|
2277
|
+
) : /* @__PURE__ */ jsx17("span", { className: cn("shrink-0 text-xs text-gray-500", classNames?.conversationLink), children: formatPhone(document2.conversationId) }),
|
|
2278
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex shrink-0 gap-1", children: [
|
|
2279
|
+
/* @__PURE__ */ jsx17(
|
|
1645
2280
|
"button",
|
|
1646
2281
|
{
|
|
1647
2282
|
type: "button",
|
|
1648
2283
|
onClick: () => void handleOpen(document2.id, "inline"),
|
|
1649
|
-
|
|
2284
|
+
"data-cv-tooltip": labels.view,
|
|
1650
2285
|
"aria-label": `${labels.view}: ${document2.filename}`,
|
|
1651
2286
|
className: "cv-header-icon",
|
|
1652
|
-
children: /* @__PURE__ */
|
|
2287
|
+
children: /* @__PURE__ */ jsx17(Eye2, { size: 14 })
|
|
1653
2288
|
}
|
|
1654
2289
|
),
|
|
1655
|
-
/* @__PURE__ */
|
|
2290
|
+
/* @__PURE__ */ jsx17(
|
|
1656
2291
|
"button",
|
|
1657
2292
|
{
|
|
1658
2293
|
type: "button",
|
|
1659
2294
|
onClick: () => void handleOpen(document2.id, "attachment"),
|
|
1660
|
-
|
|
2295
|
+
"data-cv-tooltip": labels.download,
|
|
1661
2296
|
"aria-label": `${labels.download}: ${document2.filename}`,
|
|
1662
2297
|
className: "cv-header-icon",
|
|
1663
|
-
children: /* @__PURE__ */
|
|
2298
|
+
children: /* @__PURE__ */ jsx17(Download2, { size: 14 })
|
|
1664
2299
|
}
|
|
1665
2300
|
)
|
|
1666
2301
|
] })
|
|
@@ -1669,12 +2304,14 @@ function DocumentsLibrary({
|
|
|
1669
2304
|
`${document2.conversationId}:${document2.id}`
|
|
1670
2305
|
);
|
|
1671
2306
|
}) }),
|
|
1672
|
-
total > perPage ? /* @__PURE__ */
|
|
1673
|
-
/* @__PURE__ */
|
|
1674
|
-
/* @__PURE__ */
|
|
1675
|
-
/* @__PURE__ */
|
|
2307
|
+
total > perPage ? /* @__PURE__ */ jsxs12("div", { className: cn("flex items-center justify-between border-t pt-2 text-xs dark:border-gray-700", classNames?.pagination), children: [
|
|
2308
|
+
/* @__PURE__ */ jsx17("span", { className: "text-gray-400", children: labels.total(total) }),
|
|
2309
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
|
|
2310
|
+
/* @__PURE__ */ jsx17(
|
|
1676
2311
|
"button",
|
|
1677
2312
|
{
|
|
2313
|
+
"data-cv-tooltip": PAGINATION_LABELS.previous,
|
|
2314
|
+
"aria-label": PAGINATION_LABELS.previous,
|
|
1678
2315
|
type: "button",
|
|
1679
2316
|
onClick: () => setPage(page - 1),
|
|
1680
2317
|
disabled: page <= 1,
|
|
@@ -1682,10 +2319,12 @@ function DocumentsLibrary({
|
|
|
1682
2319
|
children: "\u2039"
|
|
1683
2320
|
}
|
|
1684
2321
|
),
|
|
1685
|
-
/* @__PURE__ */
|
|
1686
|
-
/* @__PURE__ */
|
|
2322
|
+
/* @__PURE__ */ jsx17("span", { className: "text-gray-500", children: labels.page(page, lastPage) }),
|
|
2323
|
+
/* @__PURE__ */ jsx17(
|
|
1687
2324
|
"button",
|
|
1688
2325
|
{
|
|
2326
|
+
"data-cv-tooltip": PAGINATION_LABELS.next,
|
|
2327
|
+
"aria-label": PAGINATION_LABELS.next,
|
|
1689
2328
|
type: "button",
|
|
1690
2329
|
onClick: () => setPage(page + 1),
|
|
1691
2330
|
disabled: page >= lastPage,
|
|
@@ -1698,17 +2337,312 @@ function DocumentsLibrary({
|
|
|
1698
2337
|
] });
|
|
1699
2338
|
}
|
|
1700
2339
|
|
|
2340
|
+
// src/preview/ConversationSimulatorClient.ts
|
|
2341
|
+
var SIMULATOR_FILE_MEDIA_KINDS = ["image", "video", "document"];
|
|
2342
|
+
function mediaKindOf(mimeType) {
|
|
2343
|
+
if (mimeType.startsWith("image/")) return "image";
|
|
2344
|
+
if (mimeType.startsWith("video/")) return "video";
|
|
2345
|
+
if (mimeType.startsWith("audio/")) return "audio";
|
|
2346
|
+
return "document";
|
|
2347
|
+
}
|
|
2348
|
+
function acceptsMediaKind(client, kind) {
|
|
2349
|
+
if (!client.sendMedia) return false;
|
|
2350
|
+
return client.acceptedMediaKinds?.includes(kind) ?? true;
|
|
2351
|
+
}
|
|
2352
|
+
function isConversationSimulatorClient(candidate) {
|
|
2353
|
+
return typeof candidate.sendReply === "function";
|
|
2354
|
+
}
|
|
2355
|
+
function toConversationSimulatorClient({
|
|
2356
|
+
client,
|
|
2357
|
+
uploadMedia
|
|
2358
|
+
}) {
|
|
2359
|
+
const upload = uploadMedia ?? client.uploadMedia;
|
|
2360
|
+
const base = {
|
|
2361
|
+
sendText: (text) => client.sendText(text),
|
|
2362
|
+
sendReply: (selection) => {
|
|
2363
|
+
const reply = { id: selection.option.id, title: selection.option.title };
|
|
2364
|
+
return selection.kind === "button" ? client.sendButtonReply(reply) : client.sendListReply(reply);
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
if (!upload) return base;
|
|
2368
|
+
return {
|
|
2369
|
+
...base,
|
|
2370
|
+
sendMedia: async ({ mediaKind, file, mimeType, filename, caption }) => {
|
|
2371
|
+
const uploaded = await upload(file);
|
|
2372
|
+
await client.sendMedia({
|
|
2373
|
+
// O tipo sai do MIME que o upload devolveu quando ele existe: host que normaliza o formato
|
|
2374
|
+
// (áudio gravado em `webm` que sobe como `ogg`) mudava de tipo, e a mídia chegava como
|
|
2375
|
+
// documento.
|
|
2376
|
+
mediaType: uploaded.mimeType ? mediaKindOf(uploaded.mimeType) : mediaKind,
|
|
2377
|
+
mediaId: uploaded.mediaId,
|
|
2378
|
+
mimeType: uploaded.mimeType ?? mimeType ?? file.type,
|
|
2379
|
+
filename: uploaded.filename ?? filename ?? file.name,
|
|
2380
|
+
...caption ? { caption } : {}
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
// src/preview/ConversationPreview.tsx
|
|
2387
|
+
import { useCallback as useCallback6, useEffect as useEffect5, useMemo as useMemo4, useRef as useRef6, useState as useState12 } from "react";
|
|
2388
|
+
import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2389
|
+
function mediaTypeOf(mimeType) {
|
|
2390
|
+
return mediaKindOf(mimeType);
|
|
2391
|
+
}
|
|
2392
|
+
var GROUPING_WINDOW_MS = 5 * 60 * 1e3;
|
|
2393
|
+
var FOLLOW_UP_REFRESH_MS = [400, 1200, 3e3];
|
|
2394
|
+
function decorate(messages) {
|
|
2395
|
+
return messages.map((message, index) => {
|
|
2396
|
+
const previous = index > 0 ? messages[index - 1] : void 0;
|
|
2397
|
+
const currentTime = new Date(message.timestamp).getTime();
|
|
2398
|
+
const previousTime = previous ? new Date(previous.timestamp).getTime() : 0;
|
|
2399
|
+
return {
|
|
2400
|
+
message,
|
|
2401
|
+
isFirstInGroup: !previous || previous.sender !== message.sender || currentTime - previousTime > GROUPING_WINDOW_MS,
|
|
2402
|
+
showDateDivider: !previous || new Date(message.timestamp).toDateString() !== new Date(previous.timestamp).toDateString()
|
|
2403
|
+
};
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
function statusOf(error) {
|
|
2407
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
2408
|
+
const candidate = error;
|
|
2409
|
+
const value = candidate.status ?? candidate.statusCode;
|
|
2410
|
+
return typeof value === "number" ? value : void 0;
|
|
2411
|
+
}
|
|
2412
|
+
function isNotFound(error) {
|
|
2413
|
+
return statusOf(error) === 404;
|
|
2414
|
+
}
|
|
2415
|
+
function describeLoadFailure(error) {
|
|
2416
|
+
const status = statusOf(error);
|
|
2417
|
+
if (status === 401 || status === 403) {
|
|
2418
|
+
return "Sem sess\xE3o de administrador nesta aba: a mensagem \xE9 entregue no webhook, mas o transcript n\xE3o pode ser lido. Entre no painel nesta mesma aba e reabra o simulador.";
|
|
2419
|
+
}
|
|
2420
|
+
if (error instanceof Error && error.message) return `N\xE3o foi poss\xEDvel ler o transcript: ${error.message}`;
|
|
2421
|
+
return "N\xE3o foi poss\xEDvel ler o transcript da conversa.";
|
|
2422
|
+
}
|
|
2423
|
+
function ConversationPreview({
|
|
2424
|
+
client,
|
|
2425
|
+
sse,
|
|
2426
|
+
conversationId,
|
|
2427
|
+
loadMessages,
|
|
2428
|
+
placeholder,
|
|
2429
|
+
pollIntervalMs,
|
|
2430
|
+
uploadMedia
|
|
2431
|
+
}) {
|
|
2432
|
+
const [messages, setMessages] = useState12([]);
|
|
2433
|
+
const [failure, setFailure] = useState12(void 0);
|
|
2434
|
+
const [loadFailure, setLoadFailure] = useState12(void 0);
|
|
2435
|
+
const [isRecording, setIsRecording] = useState12(false);
|
|
2436
|
+
const [pendingLocal, setPendingLocal] = useState12([]);
|
|
2437
|
+
const loadMessagesRef = useRef6(loadMessages);
|
|
2438
|
+
const bottomRef = useRef6(null);
|
|
2439
|
+
loadMessagesRef.current = loadMessages;
|
|
2440
|
+
const simulator = useMemo4(
|
|
2441
|
+
() => isConversationSimulatorClient(client) ? client : toConversationSimulatorClient({ client, ...uploadMedia ? { uploadMedia } : {} }),
|
|
2442
|
+
[client, uploadMedia]
|
|
2443
|
+
);
|
|
2444
|
+
const refresh = useCallback6(async () => {
|
|
2445
|
+
try {
|
|
2446
|
+
const loaded = await loadMessagesRef.current(conversationId);
|
|
2447
|
+
setMessages(loaded);
|
|
2448
|
+
setLoadFailure(void 0);
|
|
2449
|
+
if (loaded.length > 0) setPendingLocal([]);
|
|
2450
|
+
} catch (error) {
|
|
2451
|
+
if (isNotFound(error)) {
|
|
2452
|
+
setMessages([]);
|
|
2453
|
+
setLoadFailure(void 0);
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
setLoadFailure(describeLoadFailure(error));
|
|
2457
|
+
}
|
|
2458
|
+
}, [conversationId]);
|
|
2459
|
+
useEffect5(() => {
|
|
2460
|
+
void refresh();
|
|
2461
|
+
}, [refresh]);
|
|
2462
|
+
useEffect5(() => {
|
|
2463
|
+
const source = sse.connectConversationStream(conversationId);
|
|
2464
|
+
const handler = () => {
|
|
2465
|
+
void refresh();
|
|
2466
|
+
};
|
|
2467
|
+
source.addEventListener("message", handler);
|
|
2468
|
+
return () => {
|
|
2469
|
+
source.removeEventListener("message", handler);
|
|
2470
|
+
source.close();
|
|
2471
|
+
};
|
|
2472
|
+
}, [sse, conversationId, refresh]);
|
|
2473
|
+
useEffect5(() => {
|
|
2474
|
+
if (!pollIntervalMs) return;
|
|
2475
|
+
const timer = setInterval(() => void refresh(), pollIntervalMs);
|
|
2476
|
+
return () => clearInterval(timer);
|
|
2477
|
+
}, [pollIntervalMs, refresh]);
|
|
2478
|
+
useEffect5(() => {
|
|
2479
|
+
bottomRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
2480
|
+
}, [messages]);
|
|
2481
|
+
const rendered = useMemo4(() => decorate([...messages, ...pendingLocal]), [messages, pendingLocal]);
|
|
2482
|
+
async function refreshWithFollowUps() {
|
|
2483
|
+
await refresh();
|
|
2484
|
+
for (const atraso of FOLLOW_UP_REFRESH_MS) {
|
|
2485
|
+
setTimeout(() => void refresh(), atraso);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
async function handleSend(text) {
|
|
2489
|
+
setFailure(void 0);
|
|
2490
|
+
try {
|
|
2491
|
+
await simulator.sendText(text);
|
|
2492
|
+
setPendingLocal((current) => [
|
|
2493
|
+
...current,
|
|
2494
|
+
{
|
|
2495
|
+
id: `local-${current.length}-${text.length}`,
|
|
2496
|
+
type: "text",
|
|
2497
|
+
content: text,
|
|
2498
|
+
// Do ponto de vista do servidor, mensagem do cliente é inbound — é assim que ela aparece
|
|
2499
|
+
// como "minha" nesta visão.
|
|
2500
|
+
direction: "inbound",
|
|
2501
|
+
sender: "customer",
|
|
2502
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2503
|
+
status: "sent"
|
|
2504
|
+
}
|
|
2505
|
+
]);
|
|
2506
|
+
await refreshWithFollowUps();
|
|
2507
|
+
} catch (error) {
|
|
2508
|
+
setFailure(error instanceof Error ? error.message : "Falha ao entregar a mensagem no webhook.");
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
async function handleInteractiveSelect(selection) {
|
|
2512
|
+
setFailure(void 0);
|
|
2513
|
+
try {
|
|
2514
|
+
await simulator.sendReply(selection);
|
|
2515
|
+
await refreshWithFollowUps();
|
|
2516
|
+
} catch (error) {
|
|
2517
|
+
setFailure(error instanceof Error ? error.message : "Falha ao entregar a resposta no webhook.");
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
const sendMedia = simulator.sendMedia;
|
|
2521
|
+
const canAttachFile = SIMULATOR_FILE_MEDIA_KINDS.some((kind) => acceptsMediaKind(simulator, kind));
|
|
2522
|
+
const canRecordAudio = acceptsMediaKind(simulator, "audio");
|
|
2523
|
+
async function handleAttach(file) {
|
|
2524
|
+
if (!sendMedia) return;
|
|
2525
|
+
setFailure(void 0);
|
|
2526
|
+
try {
|
|
2527
|
+
await sendMedia({
|
|
2528
|
+
mediaKind: mediaKindOf(file.type),
|
|
2529
|
+
file,
|
|
2530
|
+
mimeType: file.type,
|
|
2531
|
+
filename: file.name
|
|
2532
|
+
});
|
|
2533
|
+
await refreshWithFollowUps();
|
|
2534
|
+
} catch (error) {
|
|
2535
|
+
setFailure(error instanceof Error ? error.message : "Falha ao enviar o arquivo.");
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
return /* @__PURE__ */ jsxs13("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
2539
|
+
/* @__PURE__ */ jsxs13(ConversationWallpaper, { className: "flex-1 min-h-0 overflow-y-auto px-4 py-3", children: [
|
|
2540
|
+
rendered.map(({ message, isFirstInGroup, showDateDivider }) => /* @__PURE__ */ jsxs13("div", { children: [
|
|
2541
|
+
showDateDivider ? /* @__PURE__ */ jsx18(DateDivider, { iso: message.timestamp }) : null,
|
|
2542
|
+
/* @__PURE__ */ jsx18(
|
|
2543
|
+
MessageBubble,
|
|
2544
|
+
{
|
|
2545
|
+
message,
|
|
2546
|
+
isMine: message.direction === "inbound",
|
|
2547
|
+
isFirstInGroup,
|
|
2548
|
+
onInteractiveSelect: message.direction === "outbound" ? (selection) => void handleInteractiveSelect(selection) : void 0
|
|
2549
|
+
}
|
|
2550
|
+
)
|
|
2551
|
+
] }, message.id)),
|
|
2552
|
+
/* @__PURE__ */ jsx18("div", { ref: bottomRef })
|
|
2553
|
+
] }),
|
|
2554
|
+
failure ? /* @__PURE__ */ jsx18("p", { role: "alert", className: "px-4 py-2 text-sm text-red-600 dark:text-red-400", children: failure }) : null,
|
|
2555
|
+
loadFailure ? /* @__PURE__ */ jsx18("p", { role: "status", className: "px-4 py-2 text-sm text-amber-700 dark:text-amber-400", children: loadFailure }) : null,
|
|
2556
|
+
/* @__PURE__ */ jsx18(
|
|
2557
|
+
MessageComposer,
|
|
2558
|
+
{
|
|
2559
|
+
onSend: (text) => void handleSend(text),
|
|
2560
|
+
onAttach: canAttachFile ? (file) => void handleAttach(file) : void 0,
|
|
2561
|
+
placeholder: isRecording ? "Gravando\u2026 toque no quadrado para ouvir" : placeholder ?? "Escreva como o cliente\u2026",
|
|
2562
|
+
idleAction: canRecordAudio ? /* @__PURE__ */ jsx18(
|
|
2563
|
+
AudioRecorderButton,
|
|
2564
|
+
{
|
|
2565
|
+
onRecorded: (file) => void handleAttach(file),
|
|
2566
|
+
onFailure: (message) => setFailure(message),
|
|
2567
|
+
onRecordingChange: setIsRecording
|
|
2568
|
+
}
|
|
2569
|
+
) : void 0
|
|
2570
|
+
}
|
|
2571
|
+
)
|
|
2572
|
+
] });
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
// src/preview/ConversationSimulatorPanel.tsx
|
|
2576
|
+
import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2577
|
+
var DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS = {
|
|
2578
|
+
title: "Simulador do cliente",
|
|
2579
|
+
destinationHint: "entrega no webhook real",
|
|
2580
|
+
close: "Fechar simulador",
|
|
2581
|
+
placeholder: "Escreva como o cliente\u2026"
|
|
2582
|
+
};
|
|
2583
|
+
var SIMULATOR_PANEL_CHANNEL_WORDING = {
|
|
2584
|
+
[CONVERSATION_CHANNEL.WHATSAPP]: {
|
|
2585
|
+
destinationHint: DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS.destinationHint,
|
|
2586
|
+
placeholder: DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS.placeholder
|
|
2587
|
+
},
|
|
2588
|
+
[CONVERSATION_CHANNEL.MESSENGER]: {
|
|
2589
|
+
destinationHint: DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS.destinationHint,
|
|
2590
|
+
placeholder: DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS.placeholder
|
|
2591
|
+
},
|
|
2592
|
+
[CONVERSATION_CHANNEL.INSTAGRAM]: {
|
|
2593
|
+
destinationHint: DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS.destinationHint,
|
|
2594
|
+
placeholder: DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS.placeholder
|
|
2595
|
+
},
|
|
2596
|
+
[CONVERSATION_CHANNEL.WEBCHAT]: {
|
|
2597
|
+
destinationHint: "entrega na API do chat do site",
|
|
2598
|
+
placeholder: "Escreva como o visitante\u2026"
|
|
2599
|
+
}
|
|
2600
|
+
};
|
|
2601
|
+
function simulatorPanelLabelsOf(channel) {
|
|
2602
|
+
return {
|
|
2603
|
+
...DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS,
|
|
2604
|
+
...SIMULATOR_PANEL_CHANNEL_WORDING[channel ?? DEFAULT_CONVERSATION_CHANNEL]
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
function ConversationSimulatorPanel({
|
|
2608
|
+
onClose,
|
|
2609
|
+
channel,
|
|
2610
|
+
displayHandle,
|
|
2611
|
+
displayNumber,
|
|
2612
|
+
labels,
|
|
2613
|
+
headerActions,
|
|
2614
|
+
...previewProps
|
|
2615
|
+
}) {
|
|
2616
|
+
const text = { ...simulatorPanelLabelsOf(channel), ...labels };
|
|
2617
|
+
const subtitle = [displayHandle ?? displayNumber ?? previewProps.conversationId, text.destinationHint].join(" \xB7 ");
|
|
2618
|
+
return /* @__PURE__ */ jsxs14("aside", { className: "cv-simulator-panel", "aria-label": text.title, children: [
|
|
2619
|
+
/* @__PURE__ */ jsxs14("header", { className: "cv-simulator-panel__header", children: [
|
|
2620
|
+
/* @__PURE__ */ jsxs14("div", { className: "cv-simulator-panel__heading", children: [
|
|
2621
|
+
/* @__PURE__ */ jsx19("h2", { className: "cv-simulator-panel__title", children: text.title }),
|
|
2622
|
+
/* @__PURE__ */ jsx19("p", { className: "cv-simulator-panel__subtitle", children: subtitle })
|
|
2623
|
+
] }),
|
|
2624
|
+
/* @__PURE__ */ jsxs14("div", { className: "cv-simulator-panel__actions", children: [
|
|
2625
|
+
headerActions,
|
|
2626
|
+
/* @__PURE__ */ jsx19("button", { type: "button", onClick: onClose, "data-cv-tooltip": text.close, "aria-label": text.close, className: "cv-simulator-panel__close", children: /* @__PURE__ */ jsx19("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx19("path", { d: "M18 6 6 18M6 6l12 12" }) }) })
|
|
2627
|
+
] })
|
|
2628
|
+
] }),
|
|
2629
|
+
/* @__PURE__ */ jsx19("div", { className: "cv-simulator-panel__body", children: /* @__PURE__ */ jsx19(ConversationPreview, { ...previewProps, placeholder: text.placeholder }) })
|
|
2630
|
+
] });
|
|
2631
|
+
}
|
|
2632
|
+
|
|
1701
2633
|
export {
|
|
1702
2634
|
ConversationLocalesProvider,
|
|
1703
2635
|
useConversationLocales,
|
|
1704
2636
|
StatusTicks,
|
|
1705
2637
|
AudioPlayer,
|
|
1706
2638
|
cn,
|
|
2639
|
+
AudioTranscription,
|
|
1707
2640
|
FileIcon,
|
|
1708
2641
|
formatTimestamp,
|
|
1709
2642
|
formatDateTime,
|
|
1710
2643
|
isSameDay,
|
|
1711
2644
|
formatFileSize,
|
|
2645
|
+
documentTypeLabel,
|
|
1712
2646
|
MediaRenderer,
|
|
1713
2647
|
DEFAULT_LIGHTBOX_LABELS,
|
|
1714
2648
|
Lightbox,
|
|
@@ -1723,20 +2657,52 @@ export {
|
|
|
1723
2657
|
searchEmojis,
|
|
1724
2658
|
DEFAULT_EMOJI_PICKER_LABELS,
|
|
1725
2659
|
EmojiPicker,
|
|
2660
|
+
DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
|
|
2661
|
+
DEFAULT_MAX_RECORDING_MILLISECONDS,
|
|
2662
|
+
AudioRecorderButton,
|
|
2663
|
+
COMPOSER_BAR_CLASS,
|
|
2664
|
+
COMPOSER_TOOL_BUTTON_CLASS,
|
|
2665
|
+
COMPOSER_TOOL_BUTTON_ACTIVE_CLASS,
|
|
2666
|
+
COMPOSER_TOOL_BUTTON_IDLE_CLASS,
|
|
2667
|
+
COMPOSER_MONOSPACE_CLASS,
|
|
2668
|
+
COMPOSER_COMPACT_WIDTH,
|
|
2669
|
+
QUICK_REPLY_PILL_CLASS,
|
|
2670
|
+
applyQuickReplyVariables,
|
|
2671
|
+
resolveQuickReply,
|
|
1726
2672
|
DEFAULT_MESSAGE_COMPOSER_LABELS,
|
|
1727
2673
|
DEFAULT_ACCEPTED_FILE_TYPES,
|
|
1728
2674
|
MessageComposer,
|
|
1729
2675
|
DateDivider,
|
|
1730
2676
|
formatPhone,
|
|
1731
|
-
phoneCountryFlag,
|
|
1732
2677
|
phoneInitials,
|
|
2678
|
+
CONVERSATION_CHANNEL,
|
|
2679
|
+
DEFAULT_CONVERSATION_CHANNEL,
|
|
2680
|
+
REOPEN_MECHANISM,
|
|
2681
|
+
HANDLE_KIND,
|
|
2682
|
+
CHANNEL_CAPABILITIES,
|
|
2683
|
+
capabilitiesOf,
|
|
2684
|
+
CHANNEL_FILTER_ALL,
|
|
2685
|
+
channelFiltersFor,
|
|
2686
|
+
formatContactHandle,
|
|
2687
|
+
contactFlag,
|
|
1733
2688
|
useAsyncResource,
|
|
1734
2689
|
conversationsOf,
|
|
1735
2690
|
totalOf,
|
|
1736
2691
|
useConversationDocuments,
|
|
2692
|
+
PAGINATION_LABELS,
|
|
1737
2693
|
DOCUMENT_SOURCE_FILTER,
|
|
1738
2694
|
DEFAULT_CONVERSATION_DOCUMENTS_LABELS,
|
|
1739
2695
|
ConversationDocumentsPanel,
|
|
1740
2696
|
DEFAULT_DOCUMENTS_LIBRARY_LABELS,
|
|
1741
|
-
DocumentsLibrary
|
|
2697
|
+
DocumentsLibrary,
|
|
2698
|
+
SIMULATOR_FILE_MEDIA_KINDS,
|
|
2699
|
+
mediaKindOf,
|
|
2700
|
+
acceptsMediaKind,
|
|
2701
|
+
isConversationSimulatorClient,
|
|
2702
|
+
toConversationSimulatorClient,
|
|
2703
|
+
mediaTypeOf,
|
|
2704
|
+
ConversationPreview,
|
|
2705
|
+
DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS,
|
|
2706
|
+
simulatorPanelLabelsOf,
|
|
2707
|
+
ConversationSimulatorPanel
|
|
1742
2708
|
};
|