@adatechnology/conversations-ui 0.1.0-rc.20 → 0.1.0-rc.21
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/{chunk-OIDAIVCH.js → chunk-TV4OQRGH.js} +636 -434
- package/dist/index.d.ts +142 -9
- package/dist/index.js +374 -66
- package/dist/preview/index.d.ts +100 -3
- package/dist/preview/index.js +89 -7
- package/dist/styles.css +30 -0
- package/dist/{types-O7kMP1Yn.d.ts → types-C6A_9edv.d.ts} +42 -2
- package/package.json +2 -2
- package/src/AudioTranscription.test.tsx +115 -0
- package/src/AudioTranscription.tsx +249 -0
- package/src/ConversationContextPanel.tsx +205 -52
- package/src/ConversationLocalesProvider.tsx +28 -0
- package/src/MediaRenderer.tsx +37 -6
- package/src/MessageBubble.tsx +26 -3
- package/src/MessageComposer.tsx +21 -2
- package/src/Wallpaper.tsx +27 -13
- package/src/conversationTranscript.test.ts +57 -0
- package/src/conversationTranscript.ts +29 -4
- package/src/hooks/useScrollToLatestMessage.ts +127 -0
- package/src/index.ts +11 -0
- package/src/preview/ConversationPreview.tsx +13 -4
- package/src/preview/createPreviewBridgeClient.ts +37 -1
- package/src/preview/createPreviewMediaUploader.ts +82 -0
- package/src/preview/createPreviewWebhookClient.test.ts +89 -0
- package/src/preview/createPreviewWebhookClient.ts +91 -0
- package/src/preview/index.ts +6 -0
- package/src/preview/previewMediaUploader.test.ts +61 -0
- package/src/providers/types.ts +12 -1
- package/src/settings/TranscriptionSettingsForm.test.tsx +81 -0
- package/src/settings/TranscriptionSettingsForm.tsx +189 -0
- package/src/styles.css +37 -0
- package/src/types.ts +26 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AudioPlayer,
|
|
3
3
|
AudioRecorderButton,
|
|
4
|
+
AudioTranscription,
|
|
4
5
|
ConversationDocumentsPanel,
|
|
5
6
|
ConversationLocalesProvider,
|
|
6
7
|
ConversationWallpaper,
|
|
@@ -44,7 +45,7 @@ import {
|
|
|
44
45
|
useConversationDocuments,
|
|
45
46
|
useConversationLocales,
|
|
46
47
|
useConversations
|
|
47
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-TV4OQRGH.js";
|
|
48
49
|
import {
|
|
49
50
|
htmlToWA,
|
|
50
51
|
parseWhatsAppFormatting,
|
|
@@ -1315,6 +1316,7 @@ function ConversationHeader({
|
|
|
1315
1316
|
|
|
1316
1317
|
// src/ConversationContextPanel.tsx
|
|
1317
1318
|
import { useState as useState7 } from "react";
|
|
1319
|
+
import { Check, ChevronDown, ChevronUp, Clock, Pencil } from "lucide-react";
|
|
1318
1320
|
|
|
1319
1321
|
// src/useIsNarrow.ts
|
|
1320
1322
|
import { useEffect as useEffect4, useState as useState6 } from "react";
|
|
@@ -1336,58 +1338,189 @@ function useIsNarrow() {
|
|
|
1336
1338
|
// src/ConversationContextPanel.tsx
|
|
1337
1339
|
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1338
1340
|
var DEFAULT_CONVERSATION_CONTEXT_LABELS = {
|
|
1339
|
-
title: "
|
|
1341
|
+
title: "Suas Sele\xE7\xF5es",
|
|
1340
1342
|
empty: "Nada coletado ainda nesta conversa.",
|
|
1341
1343
|
collapse: "fechar",
|
|
1342
|
-
expand: "abrir"
|
|
1344
|
+
expand: "abrir",
|
|
1345
|
+
notCollected: "a coletar"
|
|
1343
1346
|
};
|
|
1347
|
+
function statusOf(entry) {
|
|
1348
|
+
if (entry.status) return entry.status;
|
|
1349
|
+
return entry.value ? "completed" : "pending";
|
|
1350
|
+
}
|
|
1351
|
+
var CARD_STYLE = {
|
|
1352
|
+
completed: "bg-white dark:bg-gray-800 border-2 border-green-200 dark:border-green-800 shadow-sm",
|
|
1353
|
+
editing: "bg-white dark:bg-gray-800 border-2 border-blue-200 dark:border-blue-800 shadow-sm",
|
|
1354
|
+
pending: "border-2 border-dashed border-gray-200 bg-white/40 dark:border-gray-700 dark:bg-gray-800/30"
|
|
1355
|
+
};
|
|
1356
|
+
var GLYPH_STYLE = {
|
|
1357
|
+
completed: "text-green-600 dark:text-green-400",
|
|
1358
|
+
editing: "text-blue-600 dark:text-blue-400",
|
|
1359
|
+
pending: "text-gray-400 dark:text-gray-500"
|
|
1360
|
+
};
|
|
1361
|
+
function StatusGlyph({ status }) {
|
|
1362
|
+
const size = 11;
|
|
1363
|
+
if (status === "completed") return /* @__PURE__ */ jsx13(Check, { size, "aria-hidden": true });
|
|
1364
|
+
if (status === "editing") return /* @__PURE__ */ jsx13(Pencil, { size, "aria-hidden": true });
|
|
1365
|
+
return /* @__PURE__ */ jsx13(Clock, { size, "aria-hidden": true });
|
|
1366
|
+
}
|
|
1367
|
+
function CountPill({
|
|
1368
|
+
status,
|
|
1369
|
+
count,
|
|
1370
|
+
className
|
|
1371
|
+
}) {
|
|
1372
|
+
if (count === 0) return null;
|
|
1373
|
+
return /* @__PURE__ */ jsxs11("span", { className: cn("flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium", className), children: [
|
|
1374
|
+
/* @__PURE__ */ jsx13(StatusGlyph, { status }),
|
|
1375
|
+
count
|
|
1376
|
+
] });
|
|
1377
|
+
}
|
|
1344
1378
|
function ConversationContextPanel({
|
|
1345
1379
|
entries,
|
|
1346
1380
|
defaultOpen,
|
|
1381
|
+
flowLabel,
|
|
1347
1382
|
labels: labelsOverride,
|
|
1348
1383
|
className,
|
|
1349
1384
|
classNames
|
|
1350
1385
|
}) {
|
|
1351
1386
|
const labels = { ...DEFAULT_CONVERSATION_CONTEXT_LABELS, ...labelsOverride };
|
|
1352
|
-
const
|
|
1387
|
+
const counts = { completed: 0, pending: 0, editing: 0 };
|
|
1388
|
+
for (const entry of entries) counts[statusOf(entry)] += 1;
|
|
1353
1389
|
const isNarrow = useIsNarrow();
|
|
1354
1390
|
const [manualOpen, setManualOpen] = useState7(void 0);
|
|
1355
|
-
const open = manualOpen ?? defaultOpen ?? (!isNarrow &&
|
|
1356
|
-
return
|
|
1357
|
-
|
|
1358
|
-
|
|
1391
|
+
const open = manualOpen ?? defaultOpen ?? (!isNarrow && counts.completed > 0);
|
|
1392
|
+
return (
|
|
1393
|
+
// Faixa azul clara com separador, como o wrapper do financiamento: sem ela o card encostava
|
|
1394
|
+
// direto no papel de parede da conversa e os dois blocos se misturavam.
|
|
1395
|
+
/* @__PURE__ */ jsx13("div", { className: cn("border-b bg-blue-50/60 px-3 py-3 dark:border-gray-700 dark:bg-blue-950/20", className), children: /* @__PURE__ */ jsxs11(
|
|
1396
|
+
"section",
|
|
1359
1397
|
{
|
|
1360
|
-
type: "button",
|
|
1361
|
-
onClick: () => setManualOpen(!open),
|
|
1362
|
-
"aria-expanded": open,
|
|
1363
|
-
title: open ? labels.collapse : labels.expand,
|
|
1364
1398
|
className: cn(
|
|
1365
|
-
"
|
|
1366
|
-
classNames?.
|
|
1399
|
+
"overflow-hidden rounded-lg border border-blue-200 bg-gradient-to-br from-blue-50 to-indigo-50 dark:border-blue-900 dark:from-blue-950/30 dark:to-indigo-950/30",
|
|
1400
|
+
classNames?.root
|
|
1367
1401
|
),
|
|
1368
1402
|
children: [
|
|
1369
|
-
/* @__PURE__ */
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1403
|
+
/* @__PURE__ */ jsxs11(
|
|
1404
|
+
"button",
|
|
1405
|
+
{
|
|
1406
|
+
type: "button",
|
|
1407
|
+
onClick: () => setManualOpen(!open),
|
|
1408
|
+
"aria-expanded": open,
|
|
1409
|
+
title: open ? labels.collapse : labels.expand,
|
|
1410
|
+
className: cn(
|
|
1411
|
+
"flex w-full cursor-pointer items-center justify-between gap-2 px-4 py-2.5 text-left transition-colors hover:bg-blue-100/40 dark:hover:bg-blue-900/20",
|
|
1412
|
+
classNames?.toggle
|
|
1413
|
+
),
|
|
1414
|
+
children: [
|
|
1415
|
+
/* @__PURE__ */ jsxs11("span", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1416
|
+
/* @__PURE__ */ jsx13("span", { "aria-hidden": true, className: "text-base leading-none", children: "\u{1F4CB}" }),
|
|
1417
|
+
/* @__PURE__ */ jsx13("span", { className: "truncate text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.title }),
|
|
1418
|
+
flowLabel ? /* @__PURE__ */ jsx13("span", { className: "truncate text-xs font-medium text-blue-600 dark:text-blue-400", children: flowLabel }) : null,
|
|
1419
|
+
/* @__PURE__ */ jsx13(
|
|
1420
|
+
"span",
|
|
1421
|
+
{
|
|
1422
|
+
className: cn(
|
|
1423
|
+
"inline-flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-blue-600 text-xs font-medium text-white",
|
|
1424
|
+
classNames?.counter
|
|
1425
|
+
),
|
|
1426
|
+
children: entries.length
|
|
1427
|
+
}
|
|
1428
|
+
)
|
|
1429
|
+
] }),
|
|
1430
|
+
/* @__PURE__ */ jsxs11("span", { className: "flex flex-shrink-0 items-center gap-2", children: [
|
|
1431
|
+
/* @__PURE__ */ jsx13(
|
|
1432
|
+
CountPill,
|
|
1433
|
+
{
|
|
1434
|
+
status: "completed",
|
|
1435
|
+
count: counts.completed,
|
|
1436
|
+
className: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
|
1437
|
+
}
|
|
1438
|
+
),
|
|
1439
|
+
/* @__PURE__ */ jsx13(
|
|
1440
|
+
CountPill,
|
|
1441
|
+
{
|
|
1442
|
+
status: "editing",
|
|
1443
|
+
count: counts.editing,
|
|
1444
|
+
className: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
|
1445
|
+
}
|
|
1446
|
+
),
|
|
1447
|
+
/* @__PURE__ */ jsx13(
|
|
1448
|
+
CountPill,
|
|
1449
|
+
{
|
|
1450
|
+
status: "pending",
|
|
1451
|
+
count: counts.pending,
|
|
1452
|
+
className: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
|
1453
|
+
}
|
|
1454
|
+
),
|
|
1455
|
+
open ? /* @__PURE__ */ jsx13(ChevronUp, { size: 15, className: "flex-shrink-0 text-blue-500", "aria-hidden": true }) : /* @__PURE__ */ jsx13(ChevronDown, { size: 15, className: "flex-shrink-0 text-blue-500", "aria-hidden": true })
|
|
1456
|
+
] })
|
|
1457
|
+
]
|
|
1458
|
+
}
|
|
1459
|
+
),
|
|
1460
|
+
open ? (
|
|
1461
|
+
/**
|
|
1462
|
+
* Teto de altura com rolagem interna, como no financiamento (`max-h-64 overflow-y-auto`).
|
|
1463
|
+
* Sem isso o painel cresce sem limite — e cresce de verdade: o host anexa as chaves de
|
|
1464
|
+
* contexto que ele não conhece ao fim da lista, então uma conversa com bastante estado
|
|
1465
|
+
* empurrava a conversa inteira para fora da tela.
|
|
1466
|
+
*
|
|
1467
|
+
* A diferença: lá o teto envolve o cabeçalho também, então ele rola junto e sai de vista.
|
|
1468
|
+
* Aqui só o corpo rola — o cabeçalho é o que fecha o painel, e perder o clique de fechar
|
|
1469
|
+
* no meio da rolagem é pior do que a economia de uma linha de markup.
|
|
1470
|
+
*/
|
|
1471
|
+
/* @__PURE__ */ jsx13(
|
|
1472
|
+
"div",
|
|
1473
|
+
{
|
|
1474
|
+
className: cn(
|
|
1475
|
+
"cv-scrollbar-thin max-h-64 overflow-y-auto border-t border-blue-200 px-4 pb-4 pt-1 dark:border-blue-900",
|
|
1476
|
+
classNames?.body
|
|
1477
|
+
),
|
|
1478
|
+
children: entries.length === 0 ? /* @__PURE__ */ jsx13("p", { className: "pt-2 text-xs text-gray-500 dark:text-gray-400", children: labels.empty }) : (
|
|
1479
|
+
/**
|
|
1480
|
+
* Três colunas no desktop, como no financiamento — com duas, seis campos viravam uma
|
|
1481
|
+
* coluna alta que empurrava a conversa.
|
|
1482
|
+
*
|
|
1483
|
+
* O que não fizemos igual: lá o painel FILTRA o que não foi coletado, porque a fonte
|
|
1484
|
+
* só guarda o que o cliente respondeu. Aqui a lista de campos é conhecida de antemão,
|
|
1485
|
+
* e "falta endereço" é justamente o que o atendente precisa ver ao assumir. Então o
|
|
1486
|
+
* pendente aparece — tracejado e apagado, para ler como lacuna e não como dado.
|
|
1487
|
+
*/
|
|
1488
|
+
/* @__PURE__ */ jsx13("dl", { className: "mt-2 grid grid-cols-2 gap-2 lg:grid-cols-3", children: entries.map((entry) => {
|
|
1489
|
+
const status = statusOf(entry);
|
|
1490
|
+
return /* @__PURE__ */ jsxs11(
|
|
1491
|
+
"div",
|
|
1492
|
+
{
|
|
1493
|
+
className: cn("min-w-0 rounded-lg p-2.5 transition-all", CARD_STYLE[status]),
|
|
1494
|
+
children: [
|
|
1495
|
+
/* @__PURE__ */ jsxs11("dt", { className: "mb-1 flex items-center justify-center gap-1", children: [
|
|
1496
|
+
entry.icon ? /* @__PURE__ */ jsx13("span", { "aria-hidden": true, className: "text-base leading-none", children: entry.icon }) : null,
|
|
1497
|
+
/* @__PURE__ */ jsx13("span", { className: "truncate text-[10px] font-medium uppercase leading-none tracking-wide text-gray-500 dark:text-gray-400", children: entry.label }),
|
|
1498
|
+
/* @__PURE__ */ jsx13("span", { className: cn("flex-shrink-0 leading-none", GLYPH_STYLE[status]), children: /* @__PURE__ */ jsx13(StatusGlyph, { status }) })
|
|
1499
|
+
] }),
|
|
1500
|
+
/* @__PURE__ */ jsx13(
|
|
1501
|
+
"dd",
|
|
1502
|
+
{
|
|
1503
|
+
className: cn(
|
|
1504
|
+
"truncate text-center text-xs font-semibold",
|
|
1505
|
+
entry.value ? "text-gray-900 dark:text-gray-100" : "italic font-normal text-gray-400 dark:text-gray-500"
|
|
1506
|
+
),
|
|
1507
|
+
title: entry.value ?? labels.notCollected,
|
|
1508
|
+
children: entry.value ?? labels.notCollected
|
|
1509
|
+
}
|
|
1510
|
+
)
|
|
1511
|
+
]
|
|
1512
|
+
},
|
|
1513
|
+
entry.key
|
|
1514
|
+
);
|
|
1515
|
+
}) })
|
|
1516
|
+
)
|
|
1517
|
+
}
|
|
1518
|
+
)
|
|
1519
|
+
) : null
|
|
1377
1520
|
]
|
|
1378
1521
|
}
|
|
1379
|
-
)
|
|
1380
|
-
|
|
1381
|
-
/* @__PURE__ */ jsxs11("dt", { className: "truncate text-gray-500", children: [
|
|
1382
|
-
entry.icon ? `${entry.icon} ` : "",
|
|
1383
|
-
entry.label
|
|
1384
|
-
] }),
|
|
1385
|
-
/* @__PURE__ */ jsx13("dd", { className: "truncate font-medium", children: entry.value ? /* @__PURE__ */ jsxs11("span", { className: "text-green-700 dark:text-green-400", children: [
|
|
1386
|
-
"\u2713 ",
|
|
1387
|
-
entry.value
|
|
1388
|
-
] }) : "\u2014" })
|
|
1389
|
-
] }, entry.key)) }) }) : null
|
|
1390
|
-
] });
|
|
1522
|
+
) })
|
|
1523
|
+
);
|
|
1391
1524
|
}
|
|
1392
1525
|
|
|
1393
1526
|
// src/WindowExpiredNotice.tsx
|
|
@@ -1428,13 +1561,21 @@ function buildTranscriptText(params) {
|
|
|
1428
1561
|
""
|
|
1429
1562
|
];
|
|
1430
1563
|
const lines = params.messages.map((message) => {
|
|
1431
|
-
const stamp = new Date(message.timestamp).toLocaleString("pt-BR");
|
|
1432
1564
|
const author = message.agentName ?? SENDER_LABEL[message.sender];
|
|
1433
|
-
|
|
1434
|
-
return `[${stamp}] ${author}: ${body}`;
|
|
1565
|
+
return `[${formatStamp(message.timestamp)}] ${author}: ${bodyOf(message)}`;
|
|
1435
1566
|
});
|
|
1436
1567
|
return [...header, ...lines].join("\n");
|
|
1437
1568
|
}
|
|
1569
|
+
function bodyOf(message) {
|
|
1570
|
+
const transcript = message.transcription?.text?.trim();
|
|
1571
|
+
if (message.type === "audio" && transcript) return `[\xE1udio] ${transcript}`;
|
|
1572
|
+
return message.content ?? message.caption ?? `<${message.type}>`;
|
|
1573
|
+
}
|
|
1574
|
+
function formatStamp(timestamp) {
|
|
1575
|
+
if (!timestamp) return "data indispon\xEDvel";
|
|
1576
|
+
const parsed = new Date(timestamp);
|
|
1577
|
+
return Number.isNaN(parsed.getTime()) ? "data indispon\xEDvel" : parsed.toLocaleString("pt-BR");
|
|
1578
|
+
}
|
|
1438
1579
|
function buildTranscriptFilename(whatsappNumber, generatedAt) {
|
|
1439
1580
|
const stamp = generatedAt.toISOString().slice(0, 10);
|
|
1440
1581
|
return `conversa-${whatsappNumber}-${stamp}.txt`;
|
|
@@ -2022,9 +2163,133 @@ function WelcomeFarewellForm({
|
|
|
2022
2163
|
] });
|
|
2023
2164
|
}
|
|
2024
2165
|
|
|
2166
|
+
// src/settings/TranscriptionSettingsForm.tsx
|
|
2167
|
+
import { AudioLines, Save as Save3 } from "lucide-react";
|
|
2168
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2169
|
+
var DEFAULT_LABELS5 = {
|
|
2170
|
+
sectionTitle: "Transcri\xE7\xE3o de \xE1udio",
|
|
2171
|
+
enabled: "Transcrever notas de voz",
|
|
2172
|
+
enabledHint: "Converte o \xE1udio do cliente em texto, que o atendente l\xEA e copia direto da conversa.",
|
|
2173
|
+
modeTitle: "Quando transcrever",
|
|
2174
|
+
modeAuto: "Automaticamente",
|
|
2175
|
+
modeAutoHint: "Toda nota de voz recebida \xE9 transcrita na hora. Mais c\xF4modo, consome mais cota.",
|
|
2176
|
+
modeOnDemand: "Quando o atendente pedir",
|
|
2177
|
+
modeOnDemandHint: "Transcreve s\xF3 ao clicar no bot\xE3o da conversa. Consome cota apenas com \xE1udio que algu\xE9m vai ler.",
|
|
2178
|
+
unavailable: "Transcri\xE7\xE3o n\xE3o est\xE1 dispon\xEDvel neste ambiente \u2014 fale com quem administra a instala\xE7\xE3o.",
|
|
2179
|
+
save: "Salvar",
|
|
2180
|
+
saving: "Salvando...",
|
|
2181
|
+
saveSuccess: "Configura\xE7\xE3o de transcri\xE7\xE3o salva."
|
|
2182
|
+
};
|
|
2183
|
+
function TranscriptionSettingsForm({
|
|
2184
|
+
enabled,
|
|
2185
|
+
onEnabledChange,
|
|
2186
|
+
mode,
|
|
2187
|
+
onModeChange,
|
|
2188
|
+
onSave,
|
|
2189
|
+
isAvailable = true,
|
|
2190
|
+
saving = false,
|
|
2191
|
+
saveSuccess = false,
|
|
2192
|
+
labels: labelsOverride
|
|
2193
|
+
}) {
|
|
2194
|
+
const labels = { ...DEFAULT_LABELS5, ...labelsOverride };
|
|
2195
|
+
return /* @__PURE__ */ jsxs16("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
2196
|
+
/* @__PURE__ */ jsx18("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: /* @__PURE__ */ jsxs16("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-1.5", children: [
|
|
2197
|
+
/* @__PURE__ */ jsx18(AudioLines, { size: 14, className: "text-blue-600 dark:text-blue-400" }),
|
|
2198
|
+
labels.sectionTitle
|
|
2199
|
+
] }) }),
|
|
2200
|
+
/* @__PURE__ */ jsxs16("form", { onSubmit: onSave, className: "p-6 space-y-5", children: [
|
|
2201
|
+
!isAvailable && /* @__PURE__ */ jsx18("div", { className: "bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800 rounded-xl px-4 py-3 text-sm text-amber-800 dark:text-amber-300", children: labels.unavailable }),
|
|
2202
|
+
/* @__PURE__ */ jsxs16("label", { className: "flex items-start gap-3 cursor-pointer", children: [
|
|
2203
|
+
/* @__PURE__ */ jsx18(
|
|
2204
|
+
"input",
|
|
2205
|
+
{
|
|
2206
|
+
type: "checkbox",
|
|
2207
|
+
checked: enabled,
|
|
2208
|
+
onChange: (event) => onEnabledChange(event.target.checked),
|
|
2209
|
+
disabled: !isAvailable,
|
|
2210
|
+
className: "mt-0.5 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 disabled:opacity-50"
|
|
2211
|
+
}
|
|
2212
|
+
),
|
|
2213
|
+
/* @__PURE__ */ jsxs16("span", { children: [
|
|
2214
|
+
/* @__PURE__ */ jsx18("span", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.enabled }),
|
|
2215
|
+
/* @__PURE__ */ jsx18("span", { className: "block text-xs text-gray-500 dark:text-gray-400", children: labels.enabledHint })
|
|
2216
|
+
] })
|
|
2217
|
+
] }),
|
|
2218
|
+
enabled && /* @__PURE__ */ jsxs16("fieldset", { disabled: !isAvailable, className: "space-y-2.5", children: [
|
|
2219
|
+
/* @__PURE__ */ jsx18("legend", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: labels.modeTitle }),
|
|
2220
|
+
/* @__PURE__ */ jsx18(
|
|
2221
|
+
ModeOption,
|
|
2222
|
+
{
|
|
2223
|
+
value: "onDemand",
|
|
2224
|
+
current: mode,
|
|
2225
|
+
onSelect: onModeChange,
|
|
2226
|
+
title: labels.modeOnDemand,
|
|
2227
|
+
hint: labels.modeOnDemandHint
|
|
2228
|
+
}
|
|
2229
|
+
),
|
|
2230
|
+
/* @__PURE__ */ jsx18(
|
|
2231
|
+
ModeOption,
|
|
2232
|
+
{
|
|
2233
|
+
value: "auto",
|
|
2234
|
+
current: mode,
|
|
2235
|
+
onSelect: onModeChange,
|
|
2236
|
+
title: labels.modeAuto,
|
|
2237
|
+
hint: labels.modeAutoHint
|
|
2238
|
+
}
|
|
2239
|
+
)
|
|
2240
|
+
] }),
|
|
2241
|
+
saveSuccess && /* @__PURE__ */ jsx18("div", { className: "bg-green-50 dark:bg-green-950/40 border border-green-200 dark:border-green-800 rounded-xl px-4 py-3 text-sm text-green-700 dark:text-green-400", children: labels.saveSuccess }),
|
|
2242
|
+
/* @__PURE__ */ jsx18("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs16(
|
|
2243
|
+
"button",
|
|
2244
|
+
{
|
|
2245
|
+
type: "submit",
|
|
2246
|
+
disabled: saving || !isAvailable,
|
|
2247
|
+
className: "flex items-center gap-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm",
|
|
2248
|
+
children: [
|
|
2249
|
+
saving ? /* @__PURE__ */ jsx18("span", { className: "w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx18(Save3, { size: 14 }),
|
|
2250
|
+
saving ? labels.saving : labels.save
|
|
2251
|
+
]
|
|
2252
|
+
}
|
|
2253
|
+
) })
|
|
2254
|
+
] })
|
|
2255
|
+
] });
|
|
2256
|
+
}
|
|
2257
|
+
function ModeOption({
|
|
2258
|
+
value,
|
|
2259
|
+
current,
|
|
2260
|
+
onSelect,
|
|
2261
|
+
title,
|
|
2262
|
+
hint
|
|
2263
|
+
}) {
|
|
2264
|
+
const isSelected = current === value;
|
|
2265
|
+
return /* @__PURE__ */ jsxs16(
|
|
2266
|
+
"label",
|
|
2267
|
+
{
|
|
2268
|
+
className: `flex items-start gap-3 cursor-pointer rounded-xl border px-4 py-3 transition-colors ${isSelected ? "border-blue-500 bg-blue-50 dark:border-blue-500 dark:bg-blue-950/30" : "border-gray-200 hover:border-gray-300 dark:border-gray-700 dark:hover:border-gray-600"}`,
|
|
2269
|
+
children: [
|
|
2270
|
+
/* @__PURE__ */ jsx18(
|
|
2271
|
+
"input",
|
|
2272
|
+
{
|
|
2273
|
+
type: "radio",
|
|
2274
|
+
name: "transcription-mode",
|
|
2275
|
+
value,
|
|
2276
|
+
checked: isSelected,
|
|
2277
|
+
onChange: () => onSelect(value),
|
|
2278
|
+
className: "mt-0.5 h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
2279
|
+
}
|
|
2280
|
+
),
|
|
2281
|
+
/* @__PURE__ */ jsxs16("span", { children: [
|
|
2282
|
+
/* @__PURE__ */ jsx18("span", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300", children: title }),
|
|
2283
|
+
/* @__PURE__ */ jsx18("span", { className: "block text-xs text-gray-500 dark:text-gray-400", children: hint })
|
|
2284
|
+
] })
|
|
2285
|
+
]
|
|
2286
|
+
}
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2025
2290
|
// src/settings/WhatsAppTemplatesSettings.tsx
|
|
2026
2291
|
import { useState as useState9 } from "react";
|
|
2027
|
-
import { jsx as
|
|
2292
|
+
import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2028
2293
|
var TEMPLATE_SETTINGS_TAB = {
|
|
2029
2294
|
SELECT: "select",
|
|
2030
2295
|
CREATE: "create"
|
|
@@ -2040,9 +2305,9 @@ function WhatsAppTemplatesSettings({
|
|
|
2040
2305
|
}) {
|
|
2041
2306
|
const labels = { ...DEFAULT_TEMPLATES_SETTINGS_LABELS, ...labelsOverride };
|
|
2042
2307
|
const [tab, setTab] = useState9(TEMPLATE_SETTINGS_TAB.SELECT);
|
|
2043
|
-
return /* @__PURE__ */
|
|
2044
|
-
/* @__PURE__ */
|
|
2045
|
-
/* @__PURE__ */
|
|
2308
|
+
return /* @__PURE__ */ jsxs17("div", { className: "space-y-4", children: [
|
|
2309
|
+
/* @__PURE__ */ jsxs17("nav", { className: "flex gap-1 border-b", role: "tablist", children: [
|
|
2310
|
+
/* @__PURE__ */ jsx19(
|
|
2046
2311
|
"button",
|
|
2047
2312
|
{
|
|
2048
2313
|
type: "button",
|
|
@@ -2053,7 +2318,7 @@ function WhatsAppTemplatesSettings({
|
|
|
2053
2318
|
children: labels.selectTab
|
|
2054
2319
|
}
|
|
2055
2320
|
),
|
|
2056
|
-
create ? /* @__PURE__ */
|
|
2321
|
+
create ? /* @__PURE__ */ jsx19(
|
|
2057
2322
|
"button",
|
|
2058
2323
|
{
|
|
2059
2324
|
type: "button",
|
|
@@ -2065,14 +2330,14 @@ function WhatsAppTemplatesSettings({
|
|
|
2065
2330
|
}
|
|
2066
2331
|
) : null
|
|
2067
2332
|
] }),
|
|
2068
|
-
tab === TEMPLATE_SETTINGS_TAB.SELECT ? /* @__PURE__ */
|
|
2333
|
+
tab === TEMPLATE_SETTINGS_TAB.SELECT ? /* @__PURE__ */ jsx19(WhatsAppTemplateSettingsForm, { ...settingsProps }) : create ? /* @__PURE__ */ jsx19(WhatsAppCreateTemplateForm, { ...create }) : null
|
|
2069
2334
|
] });
|
|
2070
2335
|
}
|
|
2071
2336
|
|
|
2072
2337
|
// src/settings/TopicsForm.tsx
|
|
2073
|
-
import { Megaphone, Save as
|
|
2074
|
-
import { jsx as
|
|
2075
|
-
var
|
|
2338
|
+
import { Megaphone, Save as Save4 } from "lucide-react";
|
|
2339
|
+
import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2340
|
+
var DEFAULT_LABELS6 = {
|
|
2076
2341
|
sectionTitle: "T\xF3picos intermedi\xE1rios",
|
|
2077
2342
|
sectionDescription: "Mensagens autom\xE1ticas para assuntos espec\xEDficos, disparadas quando o cliente demonstra interesse.",
|
|
2078
2343
|
messagePlaceholder: "Digite a mensagem autom\xE1tica para este t\xF3pico...",
|
|
@@ -2088,20 +2353,20 @@ function TopicsForm({
|
|
|
2088
2353
|
saveSuccess = false,
|
|
2089
2354
|
labels: labelsOverride
|
|
2090
2355
|
}) {
|
|
2091
|
-
const labels = { ...
|
|
2092
|
-
return /* @__PURE__ */
|
|
2093
|
-
/* @__PURE__ */
|
|
2094
|
-
/* @__PURE__ */
|
|
2095
|
-
/* @__PURE__ */
|
|
2356
|
+
const labels = { ...DEFAULT_LABELS6, ...labelsOverride };
|
|
2357
|
+
return /* @__PURE__ */ jsxs18("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
2358
|
+
/* @__PURE__ */ jsxs18("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
2359
|
+
/* @__PURE__ */ jsxs18("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
2360
|
+
/* @__PURE__ */ jsx20(Megaphone, { size: 16, className: "text-orange-600 dark:text-orange-400" }),
|
|
2096
2361
|
labels.sectionTitle
|
|
2097
2362
|
] }),
|
|
2098
|
-
/* @__PURE__ */
|
|
2363
|
+
/* @__PURE__ */ jsx20("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
2099
2364
|
] }),
|
|
2100
|
-
/* @__PURE__ */
|
|
2101
|
-
topics.map((topic) => /* @__PURE__ */
|
|
2102
|
-
/* @__PURE__ */
|
|
2103
|
-
/* @__PURE__ */
|
|
2104
|
-
/* @__PURE__ */
|
|
2365
|
+
/* @__PURE__ */ jsxs18("form", { onSubmit: onSave, className: "p-6 space-y-6", children: [
|
|
2366
|
+
topics.map((topic) => /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
2367
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex items-start gap-4", children: [
|
|
2368
|
+
/* @__PURE__ */ jsx20("div", { className: "flex-1", children: /* @__PURE__ */ jsx20("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: topic.label }) }),
|
|
2369
|
+
/* @__PURE__ */ jsx20(
|
|
2105
2370
|
"button",
|
|
2106
2371
|
{
|
|
2107
2372
|
type: "button",
|
|
@@ -2109,7 +2374,7 @@ function TopicsForm({
|
|
|
2109
2374
|
className: `relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${topic.enabled ? "bg-blue-600 dark:bg-blue-500" : "bg-gray-200 dark:bg-gray-700"}`,
|
|
2110
2375
|
role: "switch",
|
|
2111
2376
|
"aria-checked": topic.enabled,
|
|
2112
|
-
children: /* @__PURE__ */
|
|
2377
|
+
children: /* @__PURE__ */ jsx20(
|
|
2113
2378
|
"span",
|
|
2114
2379
|
{
|
|
2115
2380
|
className: `pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${topic.enabled ? "translate-x-5" : "translate-x-0"}`
|
|
@@ -2118,7 +2383,7 @@ function TopicsForm({
|
|
|
2118
2383
|
}
|
|
2119
2384
|
)
|
|
2120
2385
|
] }),
|
|
2121
|
-
/* @__PURE__ */
|
|
2386
|
+
/* @__PURE__ */ jsx20(
|
|
2122
2387
|
"textarea",
|
|
2123
2388
|
{
|
|
2124
2389
|
value: topic.message,
|
|
@@ -2130,20 +2395,20 @@ function TopicsForm({
|
|
|
2130
2395
|
}
|
|
2131
2396
|
)
|
|
2132
2397
|
] }, topic.key)),
|
|
2133
|
-
/* @__PURE__ */
|
|
2134
|
-
/* @__PURE__ */
|
|
2398
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-3", children: [
|
|
2399
|
+
/* @__PURE__ */ jsxs18(
|
|
2135
2400
|
"button",
|
|
2136
2401
|
{
|
|
2137
2402
|
type: "submit",
|
|
2138
2403
|
disabled: saving,
|
|
2139
2404
|
className: "inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50",
|
|
2140
2405
|
children: [
|
|
2141
|
-
/* @__PURE__ */
|
|
2406
|
+
/* @__PURE__ */ jsx20(Save4, { size: 14 }),
|
|
2142
2407
|
labels.saveButton
|
|
2143
2408
|
]
|
|
2144
2409
|
}
|
|
2145
2410
|
),
|
|
2146
|
-
saveSuccess && /* @__PURE__ */
|
|
2411
|
+
saveSuccess && /* @__PURE__ */ jsx20("span", { className: "text-sm text-green-600 dark:text-green-400", children: labels.saveSuccess })
|
|
2147
2412
|
] })
|
|
2148
2413
|
] })
|
|
2149
2414
|
] });
|
|
@@ -2188,6 +2453,46 @@ function useConversationMessages(conversationId, params) {
|
|
|
2188
2453
|
return { messages: data ?? [], loading, error, refetch, sendMessage, sendMedia, sendTemplate, markRead };
|
|
2189
2454
|
}
|
|
2190
2455
|
|
|
2456
|
+
// src/hooks/useScrollToLatestMessage.ts
|
|
2457
|
+
import { useCallback as useCallback6, useLayoutEffect, useRef as useRef5, useState as useState10 } from "react";
|
|
2458
|
+
var NEAR_BOTTOM_THRESHOLD_PX = 120;
|
|
2459
|
+
var SCROLL_BEHAVIOR = "auto";
|
|
2460
|
+
function useScrollToLatestMessage({
|
|
2461
|
+
conversationId,
|
|
2462
|
+
messageCount
|
|
2463
|
+
}) {
|
|
2464
|
+
const containerRef = useRef5(null);
|
|
2465
|
+
const [isAwayFromBottom, setIsAwayFromBottom] = useState10(false);
|
|
2466
|
+
const isAwayFromBottomRef = useRef5(false);
|
|
2467
|
+
const scrollToBottom = useCallback6((behavior = SCROLL_BEHAVIOR) => {
|
|
2468
|
+
const container = containerRef.current;
|
|
2469
|
+
if (!container) return;
|
|
2470
|
+
container.scrollTo({ top: container.scrollHeight, behavior });
|
|
2471
|
+
}, []);
|
|
2472
|
+
const handleScroll = useCallback6((event) => {
|
|
2473
|
+
const target = event.currentTarget;
|
|
2474
|
+
const distanceFromBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
|
|
2475
|
+
const away = distanceFromBottom > NEAR_BOTTOM_THRESHOLD_PX;
|
|
2476
|
+
isAwayFromBottomRef.current = away;
|
|
2477
|
+
setIsAwayFromBottom((current) => current === away ? current : away);
|
|
2478
|
+
}, []);
|
|
2479
|
+
const jumpedForConversationRef = useRef5(void 0);
|
|
2480
|
+
useLayoutEffect(() => {
|
|
2481
|
+
const isNewConversation = jumpedForConversationRef.current !== conversationId;
|
|
2482
|
+
if (isNewConversation) {
|
|
2483
|
+
isAwayFromBottomRef.current = false;
|
|
2484
|
+
setIsAwayFromBottom(false);
|
|
2485
|
+
if (messageCount === 0) return;
|
|
2486
|
+
jumpedForConversationRef.current = conversationId;
|
|
2487
|
+
scrollToBottom(SCROLL_BEHAVIOR);
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
if (isAwayFromBottomRef.current) return;
|
|
2491
|
+
scrollToBottom(SCROLL_BEHAVIOR);
|
|
2492
|
+
}, [conversationId, messageCount, scrollToBottom]);
|
|
2493
|
+
return { containerRef, handleScroll, isAwayFromBottom, scrollToBottom };
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2191
2496
|
// src/hooks/useConversationList.ts
|
|
2192
2497
|
function useConversationList(params) {
|
|
2193
2498
|
const context = useConversations();
|
|
@@ -2218,10 +2523,10 @@ function useConversationContext(conversationId) {
|
|
|
2218
2523
|
}
|
|
2219
2524
|
|
|
2220
2525
|
// src/hooks/useConversationRealtime.ts
|
|
2221
|
-
import { useEffect as useEffect6, useRef as
|
|
2526
|
+
import { useEffect as useEffect6, useRef as useRef6 } from "react";
|
|
2222
2527
|
function useConversationRealtime(conversationId, onEvent) {
|
|
2223
2528
|
const context = useConversations();
|
|
2224
|
-
const onEventRef =
|
|
2529
|
+
const onEventRef = useRef6(onEvent);
|
|
2225
2530
|
onEventRef.current = onEvent;
|
|
2226
2531
|
useEffect6(() => {
|
|
2227
2532
|
if (!context || !conversationId) return;
|
|
@@ -2236,7 +2541,7 @@ function useConversationRealtime(conversationId, onEvent) {
|
|
|
2236
2541
|
}
|
|
2237
2542
|
function useGlobalRealtime(onEvent) {
|
|
2238
2543
|
const context = useConversations();
|
|
2239
|
-
const onEventRef =
|
|
2544
|
+
const onEventRef = useRef6(onEvent);
|
|
2240
2545
|
onEventRef.current = onEvent;
|
|
2241
2546
|
useEffect6(() => {
|
|
2242
2547
|
if (!context) return;
|
|
@@ -2284,6 +2589,7 @@ function useInboxActions() {
|
|
|
2284
2589
|
export {
|
|
2285
2590
|
AudioPlayer,
|
|
2286
2591
|
AudioRecorderButton,
|
|
2592
|
+
AudioTranscription,
|
|
2287
2593
|
Avatar,
|
|
2288
2594
|
CHANNEL_BRAND_COLOR,
|
|
2289
2595
|
CHANNEL_CAPABILITIES,
|
|
@@ -2341,6 +2647,7 @@ export {
|
|
|
2341
2647
|
TEMPLATE_SETTINGS_TAB,
|
|
2342
2648
|
ToastProvider,
|
|
2343
2649
|
TopicsForm,
|
|
2650
|
+
TranscriptionSettingsForm,
|
|
2344
2651
|
WINDOW_FILTERS,
|
|
2345
2652
|
WelcomeFarewellForm,
|
|
2346
2653
|
WhatsAppCreateTemplateForm,
|
|
@@ -2383,6 +2690,7 @@ export {
|
|
|
2383
2690
|
useInboxActions,
|
|
2384
2691
|
useIsDarkTheme,
|
|
2385
2692
|
useIsNarrow,
|
|
2693
|
+
useScrollToLatestMessage,
|
|
2386
2694
|
useToast,
|
|
2387
2695
|
useWaitingNotifications,
|
|
2388
2696
|
waToHTML,
|