@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/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-OIDAIVCH.js";
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: "\u{1F4CB} Suas Sele\xE7\xF5es",
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 filled = entries.filter((entry) => Boolean(entry.value));
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 && filled.length > 0);
1356
- return /* @__PURE__ */ jsxs11("section", { className: cn("border-b", classNames?.root, className), children: [
1357
- /* @__PURE__ */ jsxs11(
1358
- "button",
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
- "flex w-full cursor-pointer items-center gap-2 px-4 py-3 text-left text-sm font-medium transition-colors hover:bg-gray-50 dark:hover:bg-gray-800",
1366
- classNames?.toggle
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__ */ jsx13("span", { "aria-hidden": true, className: "text-xs", children: open ? "\u25BE" : "\u25B8" }),
1370
- /* @__PURE__ */ jsx13("span", { children: labels.title }),
1371
- /* @__PURE__ */ jsxs11("span", { className: cn("rounded-full bg-gray-200 px-2 text-xs dark:bg-gray-700", classNames?.counter), children: [
1372
- filled.length,
1373
- "/",
1374
- entries.length
1375
- ] }),
1376
- /* @__PURE__ */ jsx13("span", { "aria-hidden": true, className: "ml-auto text-xs text-gray-500", children: open ? labels.collapse : labels.expand })
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
- open ? /* @__PURE__ */ jsx13("div", { className: cn("px-4 pb-3", classNames?.body), children: entries.length === 0 ? /* @__PURE__ */ jsx13("p", { className: "text-xs text-gray-500", children: labels.empty }) : /* @__PURE__ */ jsx13("dl", { className: "grid grid-cols-2 gap-2 text-xs", children: entries.map((entry) => /* @__PURE__ */ jsxs11("div", { className: "min-w-0", children: [
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
- const body = message.content ?? message.caption ?? `<${message.type}>`;
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 jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
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__ */ jsxs16("div", { className: "space-y-4", children: [
2044
- /* @__PURE__ */ jsxs16("nav", { className: "flex gap-1 border-b", role: "tablist", children: [
2045
- /* @__PURE__ */ jsx18(
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__ */ jsx18(
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__ */ jsx18(WhatsAppTemplateSettingsForm, { ...settingsProps }) : create ? /* @__PURE__ */ jsx18(WhatsAppCreateTemplateForm, { ...create }) : null
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 Save3 } from "lucide-react";
2074
- import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
2075
- var DEFAULT_LABELS5 = {
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 = { ...DEFAULT_LABELS5, ...labelsOverride };
2092
- return /* @__PURE__ */ jsxs17("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
2093
- /* @__PURE__ */ jsxs17("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
2094
- /* @__PURE__ */ jsxs17("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
2095
- /* @__PURE__ */ jsx19(Megaphone, { size: 16, className: "text-orange-600 dark:text-orange-400" }),
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__ */ jsx19("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
2363
+ /* @__PURE__ */ jsx20("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
2099
2364
  ] }),
2100
- /* @__PURE__ */ jsxs17("form", { onSubmit: onSave, className: "p-6 space-y-6", children: [
2101
- topics.map((topic) => /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2102
- /* @__PURE__ */ jsxs17("div", { className: "flex items-start gap-4", children: [
2103
- /* @__PURE__ */ jsx19("div", { className: "flex-1", children: /* @__PURE__ */ jsx19("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: topic.label }) }),
2104
- /* @__PURE__ */ jsx19(
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__ */ jsx19(
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__ */ jsx19(
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__ */ jsxs17("div", { className: "flex items-center gap-3", children: [
2134
- /* @__PURE__ */ jsxs17(
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__ */ jsx19(Save3, { size: 14 }),
2406
+ /* @__PURE__ */ jsx20(Save4, { size: 14 }),
2142
2407
  labels.saveButton
2143
2408
  ]
2144
2409
  }
2145
2410
  ),
2146
- saveSuccess && /* @__PURE__ */ jsx19("span", { className: "text-sm text-green-600 dark:text-green-400", children: labels.saveSuccess })
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 useRef5 } from "react";
2526
+ import { useEffect as useEffect6, useRef as useRef6 } from "react";
2222
2527
  function useConversationRealtime(conversationId, onEvent) {
2223
2528
  const context = useConversations();
2224
- const onEventRef = useRef5(onEvent);
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 = useRef5(onEvent);
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,