@customerhero/js 2.2.0 → 2.4.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/README.md CHANGED
@@ -56,6 +56,25 @@ chat.identify({
56
56
  | `locale` | `string` | Widget locale (e.g. `"en"`, `"es"`). Auto-detected from browser if omitted. |
57
57
  | `suggestedMessages` | `string[]` | Quick-reply options shown before the first message. |
58
58
 
59
+ ### Appearance
60
+
61
+ | Option | Type | Description |
62
+ | ------------------------ | ----------------------------------- | --------------------------------------------------------------------------------- |
63
+ | `colorScheme` | `"auto" \| "light" \| "dark"` | `auto` follows the visitor's OS preference. Defaults to `light`. |
64
+ | `primaryColorDark` | `string` | Primary color used in dark mode. Only honoured when the effective scheme is dark. |
65
+ | `backgroundColorDark` | `string` | Background color used in dark mode. |
66
+ | `textColorDark` | `string` | Text color used in dark mode. |
67
+ | `size` | `"compact" \| "default" \| "large"` | Launcher diameter, panel dimensions, and base font size. |
68
+ | `cornerStyle` | `"soft" \| "rounded" \| "square"` | Panel border-radius preset. |
69
+ | `launcher.iconUrl` | `string` | Custom launcher icon URL (replaces the default chat-bubble glyph). |
70
+ | `launcher.label` | `string` | CTA label next to the launcher (turns the bubble into a pill). Max 60 chars. |
71
+ | `launcher.showOnlineDot` | `boolean` | Show a small green dot on the launcher when agents are available. |
72
+ | `offset.bottom` | `number` | Pixel offset from the bottom edge. 0–1000. Defaults to 20. |
73
+ | `offset.side` | `number` | Pixel offset from the side (mirrors `position`). 0–1000. Defaults to 20. |
74
+ | `zIndex` | `number` | Z-index override. Defaults to 99999. Capped at 2 000 000 000. |
75
+
76
+ Dark colors are never auto-derived from `primaryColor`/`backgroundColor`/`textColor` — set them explicitly when enabling dark or auto modes.
77
+
59
78
  ## License
60
79
 
61
80
  MIT
package/dist/index.cjs CHANGED
@@ -20,8 +20,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ CORNER_RADIUS: () => CORNER_RADIUS,
23
24
  CustomerHeroChat: () => CustomerHeroChat,
24
25
  DEFAULTS: () => DEFAULTS,
26
+ SIZE_PRESETS: () => SIZE_PRESETS,
25
27
  SUPPORTED_LOCALES: () => SUPPORTED_LOCALES,
26
28
  ScreenshotCancelled: () => ScreenshotCancelled,
27
29
  ScreenshotUnavailable: () => ScreenshotUnavailable,
@@ -29,10 +31,14 @@ __export(index_exports, {
29
31
  captureScreenshot: () => captureScreenshot,
30
32
  createTranslator: () => createTranslator,
31
33
  detectLocale: () => detectLocale,
34
+ effectiveColors: () => effectiveColors,
32
35
  evaluate: () => evaluate,
33
36
  isRtlLocale: () => isRtlLocale,
37
+ panelRadius: () => panelRadius,
34
38
  pickFire: () => pickFire,
35
39
  resolveLocale: () => resolveLocale,
40
+ resolveScheme: () => resolveScheme,
41
+ sizePreset: () => sizePreset,
36
42
  startTriggersRuntime: () => startTriggersRuntime
37
43
  });
38
44
  module.exports = __toCommonJS(index_exports);
@@ -46,7 +52,29 @@ var DEFAULTS = {
46
52
  position: "bottom-right",
47
53
  placeholderText: "Type your message...",
48
54
  welcomeMessage: "Hi! How can I help you today?",
49
- title: "CustomerHero"
55
+ title: "CustomerHero",
56
+ // Appearance pack (B1–B6) defaults.
57
+ colorScheme: "light",
58
+ // Dark fallbacks used when the effective scheme is dark and no per-chatbot
59
+ // dark color is configured. Operators can — and should — override these.
60
+ primaryColorDark: "#A78BFA",
61
+ backgroundColorDark: "#0F172A",
62
+ textColorDark: "#E5E7EB",
63
+ size: "default",
64
+ cornerStyle: "rounded",
65
+ offsetBottom: 20,
66
+ offsetSide: 20,
67
+ zIndex: 99999
68
+ };
69
+ var SIZE_PRESETS = {
70
+ compact: { bubble: 48, width: 320, height: 480, fontSize: 13 },
71
+ default: { bubble: 56, width: 380, height: 520, fontSize: 14 },
72
+ large: { bubble: 64, width: 440, height: 600, fontSize: 15 }
73
+ };
74
+ var CORNER_RADIUS = {
75
+ soft: 8,
76
+ rounded: 16,
77
+ square: 0
50
78
  };
51
79
 
52
80
  // src/i18n/locales/en.ts
@@ -77,7 +105,10 @@ var en = {
77
105
  drop_files_here: "Drop files here",
78
106
  incident_dismiss: "Dismiss",
79
107
  incident_default_link_label: "Learn more",
80
- attachment_unsupported_type: "Unsupported file type"
108
+ attachment_unsupported_type: "Unsupported file type",
109
+ attachment_open: "Open attachment",
110
+ attachment_image_alt: "Image attachment",
111
+ attachment_location: "Shared location"
81
112
  };
82
113
 
83
114
  // src/i18n/locales/es.ts
@@ -108,7 +139,10 @@ var es = {
108
139
  drop_files_here: "Suelta los archivos aqu\xED",
109
140
  incident_dismiss: "Descartar",
110
141
  incident_default_link_label: "M\xE1s informaci\xF3n",
111
- attachment_unsupported_type: "Tipo de archivo no admitido"
142
+ attachment_unsupported_type: "Tipo de archivo no admitido",
143
+ attachment_open: "Abrir adjunto",
144
+ attachment_image_alt: "Imagen adjunta",
145
+ attachment_location: "Ubicaci\xF3n compartida"
112
146
  };
113
147
 
114
148
  // src/i18n/locales/pt-BR.ts
@@ -139,7 +173,10 @@ var ptBR = {
139
173
  drop_files_here: "Solte os arquivos aqui",
140
174
  incident_dismiss: "Dispensar",
141
175
  incident_default_link_label: "Saiba mais",
142
- attachment_unsupported_type: "Tipo de arquivo n\xE3o suportado"
176
+ attachment_unsupported_type: "Tipo de arquivo n\xE3o suportado",
177
+ attachment_open: "Abrir anexo",
178
+ attachment_image_alt: "Imagem anexada",
179
+ attachment_location: "Localiza\xE7\xE3o compartilhada"
143
180
  };
144
181
 
145
182
  // src/i18n/locales/pt-PT.ts
@@ -170,7 +207,10 @@ var ptPT = {
170
207
  drop_files_here: "Largue os ficheiros aqui",
171
208
  incident_dismiss: "Dispensar",
172
209
  incident_default_link_label: "Saiba mais",
173
- attachment_unsupported_type: "Tipo de ficheiro n\xE3o suportado"
210
+ attachment_unsupported_type: "Tipo de ficheiro n\xE3o suportado",
211
+ attachment_open: "Abrir anexo",
212
+ attachment_image_alt: "Imagem anexa",
213
+ attachment_location: "Localiza\xE7\xE3o partilhada"
174
214
  };
175
215
 
176
216
  // src/i18n/locales/fr.ts
@@ -201,7 +241,10 @@ var fr = {
201
241
  drop_files_here: "D\xE9posez les fichiers ici",
202
242
  incident_dismiss: "Ignorer",
203
243
  incident_default_link_label: "En savoir plus",
204
- attachment_unsupported_type: "Type de fichier non pris en charge"
244
+ attachment_unsupported_type: "Type de fichier non pris en charge",
245
+ attachment_open: "Ouvrir la pi\xE8ce jointe",
246
+ attachment_image_alt: "Pi\xE8ce jointe (image)",
247
+ attachment_location: "Position partag\xE9e"
205
248
  };
206
249
 
207
250
  // src/i18n/locales/de.ts
@@ -232,7 +275,10 @@ var de = {
232
275
  drop_files_here: "Dateien hier ablegen",
233
276
  incident_dismiss: "Schlie\xDFen",
234
277
  incident_default_link_label: "Mehr erfahren",
235
- attachment_unsupported_type: "Dateityp nicht unterst\xFCtzt"
278
+ attachment_unsupported_type: "Dateityp nicht unterst\xFCtzt",
279
+ attachment_open: "Anhang \xF6ffnen",
280
+ attachment_image_alt: "Bildanhang",
281
+ attachment_location: "Geteilter Standort"
236
282
  };
237
283
 
238
284
  // src/i18n/locales/it.ts
@@ -263,7 +309,10 @@ var it = {
263
309
  drop_files_here: "Trascina qui i file",
264
310
  incident_dismiss: "Chiudi",
265
311
  incident_default_link_label: "Scopri di pi\xF9",
266
- attachment_unsupported_type: "Tipo di file non supportato"
312
+ attachment_unsupported_type: "Tipo di file non supportato",
313
+ attachment_open: "Apri allegato",
314
+ attachment_image_alt: "Allegato immagine",
315
+ attachment_location: "Posizione condivisa"
267
316
  };
268
317
 
269
318
  // src/i18n/locales/nl.ts
@@ -294,7 +343,10 @@ var nl = {
294
343
  drop_files_here: "Sleep bestanden hier",
295
344
  incident_dismiss: "Sluiten",
296
345
  incident_default_link_label: "Meer informatie",
297
- attachment_unsupported_type: "Bestandstype niet ondersteund"
346
+ attachment_unsupported_type: "Bestandstype niet ondersteund",
347
+ attachment_open: "Bijlage openen",
348
+ attachment_image_alt: "Afbeeldingsbijlage",
349
+ attachment_location: "Gedeelde locatie"
298
350
  };
299
351
 
300
352
  // src/i18n/locales/pl.ts
@@ -325,7 +377,10 @@ var pl = {
325
377
  drop_files_here: "Upu\u015B\u0107 pliki tutaj",
326
378
  incident_dismiss: "Zamknij",
327
379
  incident_default_link_label: "Dowiedz si\u0119 wi\u0119cej",
328
- attachment_unsupported_type: "Nieobs\u0142ugiwany typ pliku"
380
+ attachment_unsupported_type: "Nieobs\u0142ugiwany typ pliku",
381
+ attachment_open: "Otw\xF3rz za\u0142\u0105cznik",
382
+ attachment_image_alt: "Za\u0142\u0105cznik obrazu",
383
+ attachment_location: "Udost\u0119pniona lokalizacja"
329
384
  };
330
385
 
331
386
  // src/i18n/locales/tr.ts
@@ -356,7 +411,10 @@ var tr = {
356
411
  drop_files_here: "Dosyalar\u0131 buraya b\u0131rak",
357
412
  incident_dismiss: "Kapat",
358
413
  incident_default_link_label: "Daha fazla bilgi",
359
- attachment_unsupported_type: "Desteklenmeyen dosya t\xFCr\xFC"
414
+ attachment_unsupported_type: "Desteklenmeyen dosya t\xFCr\xFC",
415
+ attachment_open: "Eki a\xE7",
416
+ attachment_image_alt: "Resim eki",
417
+ attachment_location: "Payla\u015F\u0131lan konum"
360
418
  };
361
419
 
362
420
  // src/i18n/locales/ar.ts
@@ -387,7 +445,10 @@ var ar = {
387
445
  drop_files_here: "\u0623\u0641\u0644\u062A \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0647\u0646\u0627",
388
446
  incident_dismiss: "\u0625\u063A\u0644\u0627\u0642",
389
447
  incident_default_link_label: "\u0627\u0639\u0631\u0641 \u0627\u0644\u0645\u0632\u064A\u062F",
390
- attachment_unsupported_type: "\u0646\u0648\u0639 \u0627\u0644\u0645\u0644\u0641 \u063A\u064A\u0631 \u0645\u062F\u0639\u0648\u0645"
448
+ attachment_unsupported_type: "\u0646\u0648\u0639 \u0627\u0644\u0645\u0644\u0641 \u063A\u064A\u0631 \u0645\u062F\u0639\u0648\u0645",
449
+ attachment_open: "\u0641\u062A\u062D \u0627\u0644\u0645\u0631\u0641\u0642",
450
+ attachment_image_alt: "\u0635\u0648\u0631\u0629 \u0645\u0631\u0641\u0642\u0629",
451
+ attachment_location: "\u0645\u0648\u0642\u0639 \u0645\u0634\u062A\u0631\u0643"
391
452
  };
392
453
 
393
454
  // src/i18n/locales/ja.ts
@@ -418,7 +479,10 @@ var ja = {
418
479
  drop_files_here: "\u30D5\u30A1\u30A4\u30EB\u3092\u3053\u3053\u306B\u30C9\u30ED\u30C3\u30D7",
419
480
  incident_dismiss: "\u9589\u3058\u308B",
420
481
  incident_default_link_label: "\u8A73\u7D30\u3092\u898B\u308B",
421
- attachment_unsupported_type: "\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F"
482
+ attachment_unsupported_type: "\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F",
483
+ attachment_open: "\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB\u3092\u958B\u304F",
484
+ attachment_image_alt: "\u753B\u50CF\u306E\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB",
485
+ attachment_location: "\u5171\u6709\u3055\u308C\u305F\u4F4D\u7F6E\u60C5\u5831"
422
486
  };
423
487
 
424
488
  // src/i18n/locales/ko.ts
@@ -449,7 +513,10 @@ var ko = {
449
513
  drop_files_here: "\uD30C\uC77C\uC744 \uC5EC\uAE30\uC5D0 \uB193\uAE30",
450
514
  incident_dismiss: "\uB2EB\uAE30",
451
515
  incident_default_link_label: "\uC790\uC138\uD788 \uC54C\uC544\uBCF4\uAE30",
452
- attachment_unsupported_type: "\uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD"
516
+ attachment_unsupported_type: "\uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD",
517
+ attachment_open: "\uCCA8\uBD80 \uD30C\uC77C \uC5F4\uAE30",
518
+ attachment_image_alt: "\uC774\uBBF8\uC9C0 \uCCA8\uBD80 \uD30C\uC77C",
519
+ attachment_location: "\uACF5\uC720\uB41C \uC704\uCE58"
453
520
  };
454
521
 
455
522
  // src/i18n/locales/zh-CN.ts
@@ -480,7 +547,10 @@ var zhCN = {
480
547
  drop_files_here: "\u5C06\u6587\u4EF6\u62D6\u653E\u5230\u6B64\u5904",
481
548
  incident_dismiss: "\u5173\u95ED",
482
549
  incident_default_link_label: "\u4E86\u89E3\u66F4\u591A",
483
- attachment_unsupported_type: "\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B"
550
+ attachment_unsupported_type: "\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B",
551
+ attachment_open: "\u6253\u5F00\u9644\u4EF6",
552
+ attachment_image_alt: "\u56FE\u7247\u9644\u4EF6",
553
+ attachment_location: "\u5171\u4EAB\u7684\u4F4D\u7F6E"
484
554
  };
485
555
 
486
556
  // src/i18n/locales/zh-TW.ts
@@ -511,7 +581,10 @@ var zhTW = {
511
581
  drop_files_here: "\u5C07\u6A94\u6848\u62D6\u653E\u5230\u6B64\u8655",
512
582
  incident_dismiss: "\u95DC\u9589",
513
583
  incident_default_link_label: "\u77AD\u89E3\u66F4\u591A",
514
- attachment_unsupported_type: "\u4E0D\u652F\u63F4\u7684\u6A94\u6848\u985E\u578B"
584
+ attachment_unsupported_type: "\u4E0D\u652F\u63F4\u7684\u6A94\u6848\u985E\u578B",
585
+ attachment_open: "\u958B\u555F\u9644\u4EF6",
586
+ attachment_image_alt: "\u5716\u7247\u9644\u4EF6",
587
+ attachment_location: "\u5171\u4EAB\u7684\u4F4D\u7F6E"
515
588
  };
516
589
 
517
590
  // src/i18n/index.ts
@@ -1014,7 +1087,14 @@ function startTriggersRuntime(options) {
1014
1087
  }
1015
1088
 
1016
1089
  // src/client.ts
1090
+ function clampInt(value, min, max, fallback) {
1091
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
1092
+ return Math.max(min, Math.min(max, Math.trunc(value)));
1093
+ }
1017
1094
  function resolveConfig(userConfig, fetched) {
1095
+ const launcherUser = userConfig.launcher ?? {};
1096
+ const launcherFetched = fetched?.launcher ?? {};
1097
+ const offsetUser = userConfig.offset ?? {};
1018
1098
  return {
1019
1099
  chatbotId: userConfig.chatbotId,
1020
1100
  apiBase: userConfig.apiBase ?? DEFAULTS.apiBase,
@@ -1027,7 +1107,32 @@ function resolveConfig(userConfig, fetched) {
1027
1107
  title: userConfig.title ?? fetched?.title ?? DEFAULTS.title,
1028
1108
  avatarUrl: userConfig.avatarUrl ?? fetched?.avatarUrl,
1029
1109
  suggestedMessages: userConfig.suggestedMessages ?? fetched?.suggestedMessages ?? [],
1030
- stringOverrides: fetched?.stringOverrides
1110
+ stringOverrides: fetched?.stringOverrides,
1111
+ // Appearance pack. Color palette + size + corner style + launcher all
1112
+ // come from the server widget_config (with host-side override). The
1113
+ // *runtime* knobs — colorScheme, offset, zIndex — are host-only because
1114
+ // they depend on the page the widget is embedded in, not the chatbot.
1115
+ primaryColorDark: userConfig.primaryColorDark ?? fetched?.primaryColorDark,
1116
+ backgroundColorDark: userConfig.backgroundColorDark ?? fetched?.backgroundColorDark,
1117
+ textColorDark: userConfig.textColorDark ?? fetched?.textColorDark,
1118
+ size: userConfig.size ?? fetched?.size ?? DEFAULTS.size,
1119
+ cornerStyle: userConfig.cornerStyle ?? fetched?.cornerStyle ?? DEFAULTS.cornerStyle,
1120
+ launcher: {
1121
+ iconUrl: launcherUser.iconUrl ?? launcherFetched.iconUrl,
1122
+ label: launcherUser.label ?? launcherFetched.label,
1123
+ showOnlineDot: launcherUser.showOnlineDot ?? launcherFetched.showOnlineDot ?? false
1124
+ },
1125
+ colorScheme: userConfig.colorScheme ?? DEFAULTS.colorScheme,
1126
+ offset: {
1127
+ bottom: clampInt(offsetUser.bottom, 0, 1e3, DEFAULTS.offsetBottom),
1128
+ side: clampInt(offsetUser.side, 0, 1e3, DEFAULTS.offsetSide)
1129
+ },
1130
+ zIndex: clampInt(userConfig.zIndex, 0, 2e9, DEFAULTS.zIndex),
1131
+ // Defaults to true — the widget shows the attach button unless the
1132
+ // chatbot explicitly opts out via widget_config or the host passes
1133
+ // allowAttachments=false. Server still enforces the same flag at the
1134
+ // upload endpoint either way.
1135
+ allowAttachments: userConfig.allowAttachments ?? fetched?.allowAttachments ?? true
1031
1136
  };
1032
1137
  }
1033
1138
  function sanitizeIncidentBanner(input) {
@@ -1117,9 +1222,40 @@ var CustomerHeroChat = class {
1117
1222
  pendingTriggerId: null,
1118
1223
  pendingPrefill: null,
1119
1224
  incidentBanner: null,
1120
- incidentBannerDismissed: false
1225
+ incidentBannerDismissed: false,
1226
+ readOnly: false
1121
1227
  };
1122
1228
  }
1229
+ /**
1230
+ * Mark the config as loaded and put the client into read-only preview
1231
+ * mode without hitting the API. Used by `@customerhero/react/preview` to
1232
+ * render the widget against a host-supplied config (the dashboard preview
1233
+ * pane). Public API consumers should not call this.
1234
+ *
1235
+ * Pass a config to re-resolve and update the rendered colors/size/launcher
1236
+ * in place. Callers should reuse the same client instance across config
1237
+ * changes so the open animation only fires once.
1238
+ *
1239
+ * @internal
1240
+ */
1241
+ __seedForPreview(config, extras) {
1242
+ const resolved = config ? resolveConfig(config) : this.state.config;
1243
+ if (config) this.userConfig = config;
1244
+ const seededMessages = resolved.welcomeMessage ? [{ role: "bot", content: resolved.welcomeMessage }] : [];
1245
+ const sanitizedBanner = extras && "banner" in extras ? sanitizeIncidentBanner(extras.banner ?? null) : this.state.incidentBanner;
1246
+ this.setState({
1247
+ config: resolved,
1248
+ configLoaded: true,
1249
+ configError: null,
1250
+ readOnly: true,
1251
+ isOpen: true,
1252
+ messages: seededMessages,
1253
+ incidentBanner: sanitizedBanner,
1254
+ // Reset the dismissed flag so toggling the banner on in the dashboard
1255
+ // re-renders it after a previous preview-side dismiss.
1256
+ incidentBannerDismissed: false
1257
+ });
1258
+ }
1123
1259
  // ── Proactive engagement state ─────────────────────────────────────
1124
1260
  triggersRuntime = null;
1125
1261
  preChatFormSubmitted = false;
@@ -1228,12 +1364,15 @@ var CustomerHeroChat = class {
1228
1364
  const { chatbotId, apiBase } = this.state.config;
1229
1365
  const { conversationId } = this.state;
1230
1366
  if (!conversationId) return;
1367
+ const readToken = this.storage?.getItem(`ch_conv_token_${chatbotId}`);
1368
+ const messagesUrl = `${apiBase}/api/chat/${chatbotId}/messages/${conversationId}`;
1231
1369
  try {
1232
1370
  const response = await fetch(
1233
- `${apiBase}/api/chat/${chatbotId}/messages/${conversationId}`
1371
+ readToken ? `${messagesUrl}?t=${encodeURIComponent(readToken)}` : messagesUrl
1234
1372
  );
1235
1373
  if (!response.ok) {
1236
1374
  this.storage?.removeItem(`ch_conv_${chatbotId}`);
1375
+ this.storage?.removeItem(`ch_conv_token_${chatbotId}`);
1237
1376
  this.setState({ conversationId: null });
1238
1377
  return;
1239
1378
  }
@@ -1245,7 +1384,8 @@ var CustomerHeroChat = class {
1245
1384
  content: m.content,
1246
1385
  ...m.sources ? { sources: m.sources } : {},
1247
1386
  ...m.blocks ? { blocks: m.blocks } : {},
1248
- ...m.suggestions ? { suggestions: m.suggestions } : {}
1387
+ ...m.suggestions ? { suggestions: m.suggestions } : {},
1388
+ ...m.attachments?.length ? { attachments: m.attachments } : {}
1249
1389
  }));
1250
1390
  const lastBotIndex = findLastIndex(
1251
1391
  messages,
@@ -1265,6 +1405,7 @@ var CustomerHeroChat = class {
1265
1405
  }
1266
1406
  }
1267
1407
  async sendMessage(message, options) {
1408
+ if (this.state.readOnly) return;
1268
1409
  const trimmed = message.trim();
1269
1410
  const attachmentTokens = options?.attachmentTokens ?? [];
1270
1411
  if (!trimmed || this.state.isLoading) return;
@@ -1348,6 +1489,16 @@ var CustomerHeroChat = class {
1348
1489
  }
1349
1490
  break;
1350
1491
  }
1492
+ case "read-token": {
1493
+ const tok = safeParse(evt.data);
1494
+ if (tok?.readToken) {
1495
+ this.storage?.setItem(
1496
+ `ch_conv_token_${chatbotId}`,
1497
+ tok.readToken
1498
+ );
1499
+ }
1500
+ break;
1501
+ }
1351
1502
  case "token": {
1352
1503
  const tok = safeParse(evt.data);
1353
1504
  const text = tok?.text ?? "";
@@ -1460,6 +1611,9 @@ var CustomerHeroChat = class {
1460
1611
  await this.loadHistory();
1461
1612
  return;
1462
1613
  }
1614
+ const decisionBlock = this.state.messages[targetIndex].blocks?.find(
1615
+ (b) => b.type === "action_confirmation" && b.pendingToolCallId === pendingId
1616
+ );
1463
1617
  const messages = this.state.messages.slice();
1464
1618
  const original = messages[targetIndex];
1465
1619
  messages[targetIndex] = {
@@ -1468,7 +1622,8 @@ var CustomerHeroChat = class {
1468
1622
  };
1469
1623
  this.setState({ messages, error: null });
1470
1624
  const { chatbotId, apiBase } = this.state.config;
1471
- const url = `${apiBase}/api/chat/${chatbotId}/tool-calls/${pendingId}/decision`;
1625
+ const href = decision === "approve" ? decisionBlock?.approveHref : decisionBlock?.cancelHref;
1626
+ const url = href ? `${apiBase}${href}` : `${apiBase}/api/chat/${chatbotId}/tool-calls/${pendingId}/decision`;
1472
1627
  try {
1473
1628
  const response = await fetch(url, {
1474
1629
  method: "POST",
@@ -1500,6 +1655,16 @@ var CustomerHeroChat = class {
1500
1655
  }
1501
1656
  break;
1502
1657
  }
1658
+ case "read-token": {
1659
+ const tok = safeParse(evt.data);
1660
+ if (tok?.readToken) {
1661
+ this.storage?.setItem(
1662
+ `ch_conv_token_${chatbotId}`,
1663
+ tok.readToken
1664
+ );
1665
+ }
1666
+ break;
1667
+ }
1503
1668
  case "token": {
1504
1669
  const tok = safeParse(evt.data);
1505
1670
  const text = tok?.text ?? "";
@@ -1606,6 +1771,7 @@ var CustomerHeroChat = class {
1606
1771
  reset() {
1607
1772
  const { chatbotId, welcomeMessage } = this.state.config;
1608
1773
  this.storage?.removeItem(`ch_conv_${chatbotId}`);
1774
+ this.storage?.removeItem(`ch_conv_token_${chatbotId}`);
1609
1775
  this.setState({
1610
1776
  messages: welcomeMessage ? [{ role: "bot", content: welcomeMessage }] : [],
1611
1777
  conversationId: null,
@@ -1773,6 +1939,7 @@ var CustomerHeroChat = class {
1773
1939
  };
1774
1940
  const { chatbotId, welcomeMessage } = this.state.config;
1775
1941
  this.storage?.removeItem(`ch_conv_${chatbotId}`);
1942
+ this.storage?.removeItem(`ch_conv_token_${chatbotId}`);
1776
1943
  this.setState({
1777
1944
  messages: welcomeMessage ? [{ role: "bot", content: welcomeMessage }] : [],
1778
1945
  conversationId: null,
@@ -1817,6 +1984,33 @@ function pickExtension(mime) {
1817
1984
  return "jpg";
1818
1985
  }
1819
1986
 
1987
+ // src/theme.ts
1988
+ function resolveScheme(colorScheme, prefersDark) {
1989
+ if (colorScheme === "dark") return "dark";
1990
+ if (colorScheme === "light") return "light";
1991
+ return prefersDark ? "dark" : "light";
1992
+ }
1993
+ function effectiveColors(config, scheme) {
1994
+ if (scheme === "dark") {
1995
+ return {
1996
+ primary: config.primaryColorDark ?? DEFAULTS.primaryColorDark,
1997
+ background: config.backgroundColorDark ?? DEFAULTS.backgroundColorDark,
1998
+ text: config.textColorDark ?? DEFAULTS.textColorDark
1999
+ };
2000
+ }
2001
+ return {
2002
+ primary: config.primaryColor,
2003
+ background: config.backgroundColor,
2004
+ text: config.textColor
2005
+ };
2006
+ }
2007
+ function sizePreset(size) {
2008
+ return SIZE_PRESETS[size];
2009
+ }
2010
+ function panelRadius(cornerStyle) {
2011
+ return CORNER_RADIUS[cornerStyle];
2012
+ }
2013
+
1820
2014
  // src/screenshot.ts
1821
2015
  var ScreenshotCancelled = class extends Error {
1822
2016
  constructor(message = "Screenshot cancelled") {
@@ -1951,8 +2145,10 @@ async function canvasToBlob(canvas, quality) {
1951
2145
  }
1952
2146
  // Annotate the CommonJS export names for ESM import in node:
1953
2147
  0 && (module.exports = {
2148
+ CORNER_RADIUS,
1954
2149
  CustomerHeroChat,
1955
2150
  DEFAULTS,
2151
+ SIZE_PRESETS,
1956
2152
  SUPPORTED_LOCALES,
1957
2153
  ScreenshotCancelled,
1958
2154
  ScreenshotUnavailable,
@@ -1960,9 +2156,13 @@ async function canvasToBlob(canvas, quality) {
1960
2156
  captureScreenshot,
1961
2157
  createTranslator,
1962
2158
  detectLocale,
2159
+ effectiveColors,
1963
2160
  evaluate,
1964
2161
  isRtlLocale,
2162
+ panelRadius,
1965
2163
  pickFire,
1966
2164
  resolveLocale,
2165
+ resolveScheme,
2166
+ sizePreset,
1967
2167
  startTriggersRuntime
1968
2168
  });