@helpai/elements 0.29.1 → 0.30.1
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/elements-web-component.esm.js +29 -29
- package/elements-web-component.esm.js.map +4 -4
- package/elements.cjs.js +24 -24
- package/elements.cjs.js.map +4 -4
- package/elements.esm.js +24 -24
- package/elements.esm.js.map +4 -4
- package/elements.js +24 -24
- package/elements.js.map +4 -4
- package/index.d.ts +59 -30
- package/index.mjs +600 -523
- package/package.json +1 -1
- package/schema.d.ts +49 -49
- package/web-component.d.ts +11 -5
- package/web-component.mjs +399 -325
package/web-component.mjs
CHANGED
|
@@ -3,11 +3,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
5
|
// src/web-component.ts
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
// src/ui/app.tsx
|
|
9
|
-
import { useCallback as useCallback6, useEffect as useEffect17, useMemo as useMemo3, useRef as useRef9, useState as useState13 } from "preact/hooks";
|
|
10
|
-
import { useComputed as useComputed7, useSignal as useSignal2 } from "@preact/signals";
|
|
6
|
+
import { render } from "preact";
|
|
11
7
|
|
|
12
8
|
// src/core/brand.ts
|
|
13
9
|
var BRAND = {
|
|
@@ -509,8 +505,9 @@ function resolveOptions(rawOpts) {
|
|
|
509
505
|
const header = opts.header ?? {};
|
|
510
506
|
const { themeMode, themeOverrides } = parseTheme(opts.theme);
|
|
511
507
|
const mode = presentation.mode ?? "floating";
|
|
512
|
-
const
|
|
513
|
-
const availableLocales = i18n.availableLocales ?? [locale];
|
|
508
|
+
const defaultLocale = resolveDefaultLocale(i18n.defaultLocale);
|
|
509
|
+
const availableLocales = i18n.availableLocales ?? [i18n.locale ?? defaultLocale];
|
|
510
|
+
const locale = clampLocale(i18n.locale ?? defaultLocale, availableLocales, defaultLocale);
|
|
514
511
|
const baseUrl = (opts.baseUrl ?? BRAND.defaultBaseUrl).replace(/\/$/, "");
|
|
515
512
|
const agentApiBaseUrl = (opts.agentApiBaseUrl ?? `${baseUrl}/api/agent`).replace(/\/$/, "");
|
|
516
513
|
const dataApiBaseUrl = (opts.dataApiBaseUrl ?? `${baseUrl}/api/data`).replace(/\/$/, "");
|
|
@@ -552,6 +549,7 @@ function resolveOptions(rawOpts) {
|
|
|
552
549
|
maxCount: opts.composer?.attachments?.maxCount ?? DEFAULT_ATTACHMENTS.maxCount
|
|
553
550
|
},
|
|
554
551
|
locale,
|
|
552
|
+
defaultLocale,
|
|
555
553
|
availableLocales,
|
|
556
554
|
startMinimized: behavior.startMinimized ?? true,
|
|
557
555
|
actions: resolveActions(mode, header.actions),
|
|
@@ -582,6 +580,11 @@ function resolveDefaultLocale(input) {
|
|
|
582
580
|
if (input && input !== "auto") return input;
|
|
583
581
|
return typeof navigator !== "undefined" && navigator.language ? navigator.language : "en";
|
|
584
582
|
}
|
|
583
|
+
function clampLocale(desired, availableLocales, defaultLocale) {
|
|
584
|
+
if (availableLocales.length <= 1 || availableLocales.includes(desired)) return desired;
|
|
585
|
+
if (availableLocales.includes(defaultLocale)) return defaultLocale;
|
|
586
|
+
return availableLocales[0] ?? desired;
|
|
587
|
+
}
|
|
585
588
|
function resolveActions(mode, overrides) {
|
|
586
589
|
return overrides ?? DEFAULT_ACTIONS_BY_MODE[mode];
|
|
587
590
|
}
|
|
@@ -730,13 +733,14 @@ function resolveLauncher(overrides) {
|
|
|
730
733
|
|
|
731
734
|
// src/core/config/overrides.ts
|
|
732
735
|
function applyOptionOverrides(options, activeLocale, activeThemeMode, activeTextSize) {
|
|
733
|
-
|
|
736
|
+
const locale = clampLocale(activeLocale, options.availableLocales, options.defaultLocale);
|
|
737
|
+
if (locale === options.locale && activeThemeMode === options.themeMode && activeTextSize === options.textSize) {
|
|
734
738
|
return options;
|
|
735
739
|
}
|
|
736
740
|
const next = { ...options };
|
|
737
|
-
if (
|
|
738
|
-
next.locale =
|
|
739
|
-
next.strings = resolveStrings(
|
|
741
|
+
if (locale !== options.locale) {
|
|
742
|
+
next.locale = locale;
|
|
743
|
+
next.strings = resolveStrings(locale, options.stringsOverride);
|
|
740
744
|
}
|
|
741
745
|
if (activeThemeMode !== options.themeMode) next.themeMode = activeThemeMode;
|
|
742
746
|
if (activeTextSize !== options.textSize) next.textSize = activeTextSize;
|
|
@@ -782,6 +786,8 @@ function mergeServerConfig(user, server) {
|
|
|
782
786
|
serverI18n ?? {},
|
|
783
787
|
userI18n
|
|
784
788
|
);
|
|
789
|
+
if (serverI18n?.availableLocales !== void 0) merged.availableLocales = serverI18n.availableLocales;
|
|
790
|
+
if (serverI18n?.defaultLocale !== void 0) merged.defaultLocale = serverI18n.defaultLocale;
|
|
785
791
|
const userStrings = userI18n?.strings;
|
|
786
792
|
const serverStrings = serverI18n?.strings;
|
|
787
793
|
if (userStrings || serverStrings) {
|
|
@@ -983,35 +989,153 @@ function parseLauncher(get, str2) {
|
|
|
983
989
|
return Object.keys(base).length > 0 ? base : null;
|
|
984
990
|
}
|
|
985
991
|
|
|
986
|
-
// src/core/
|
|
987
|
-
function
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
+
// src/core/ids.ts
|
|
993
|
+
function uuid7() {
|
|
994
|
+
const ts = Date.now();
|
|
995
|
+
const bytes = new Uint8Array(16);
|
|
996
|
+
bytes[0] = Math.floor(ts / 2 ** 40) & 255;
|
|
997
|
+
bytes[1] = Math.floor(ts / 2 ** 32) & 255;
|
|
998
|
+
bytes[2] = ts >>> 24 & 255;
|
|
999
|
+
bytes[3] = ts >>> 16 & 255;
|
|
1000
|
+
bytes[4] = ts >>> 8 & 255;
|
|
1001
|
+
bytes[5] = ts & 255;
|
|
1002
|
+
fillRandom(bytes.subarray(6));
|
|
1003
|
+
bytes[6] = bytes[6] & 15 | 112;
|
|
1004
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1005
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1006
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
992
1007
|
}
|
|
993
|
-
function
|
|
994
|
-
if (
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1008
|
+
function fillRandom(view) {
|
|
1009
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
1010
|
+
crypto.getRandomValues(view);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
for (let i = 0; i < view.length; i++) view[i] = Math.floor(Math.random() * 256);
|
|
998
1014
|
}
|
|
999
1015
|
|
|
1000
|
-
// src/core/
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
const
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1016
|
+
// src/core/persistence.ts
|
|
1017
|
+
var log2 = logger.scope("persistence");
|
|
1018
|
+
function createPersistence(widgetId, storage = defaultStorage) {
|
|
1019
|
+
const prefix = `${BRAND.cssPrefix}.${widgetId}`;
|
|
1020
|
+
const KEY_VISITOR = `${prefix}.visitorId`;
|
|
1021
|
+
const KEY_CONVERSATION = `${prefix}.conversationId`;
|
|
1022
|
+
const KEY_USER_PREFS = `${prefix}.userPrefs`;
|
|
1023
|
+
const KEY_PANEL_OPEN = `${prefix}.panelOpen`;
|
|
1024
|
+
const KEY_PANEL_SIZE = `${prefix}.panelSize`;
|
|
1025
|
+
const KEY_CALLOUT_DISMISSED = `${prefix}.calloutDismissed`;
|
|
1026
|
+
const KEY_SIDEBAR_COLLAPSED = `${prefix}.sidebarCollapsed`;
|
|
1027
|
+
const KEY_ACTIVE_MODULE = `${prefix}.activeModule`;
|
|
1028
|
+
const KEY_FORMS_DONE = `${prefix}.formsDone`;
|
|
1029
|
+
const persistence = {
|
|
1030
|
+
getVisitorId() {
|
|
1031
|
+
const existing = storage.get(KEY_VISITOR);
|
|
1032
|
+
if (existing) return existing;
|
|
1033
|
+
const minted = uuid7();
|
|
1034
|
+
storage.set(KEY_VISITOR, minted);
|
|
1035
|
+
return minted;
|
|
1036
|
+
},
|
|
1037
|
+
setVisitorId(id) {
|
|
1038
|
+
log2.debug("setVisitorId (rebind)", { id, widgetId });
|
|
1039
|
+
storage.set(KEY_VISITOR, id);
|
|
1040
|
+
},
|
|
1041
|
+
loadConversationId() {
|
|
1042
|
+
return storage.get(KEY_CONVERSATION) ?? void 0;
|
|
1043
|
+
},
|
|
1044
|
+
saveConversationId(id) {
|
|
1045
|
+
log2.trace("saveConversationId", { id, widgetId });
|
|
1046
|
+
if (id) storage.set(KEY_CONVERSATION, id);
|
|
1047
|
+
else storage.remove(KEY_CONVERSATION);
|
|
1048
|
+
},
|
|
1049
|
+
loadUserPrefs() {
|
|
1050
|
+
const raw = storage.get(KEY_USER_PREFS);
|
|
1051
|
+
if (!raw) return {};
|
|
1052
|
+
try {
|
|
1053
|
+
const parsed = JSON.parse(raw);
|
|
1054
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
log2.warn("loadUserPrefs parse failed", { error });
|
|
1057
|
+
return {};
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
saveUserPrefs(s) {
|
|
1061
|
+
try {
|
|
1062
|
+
storage.set(KEY_USER_PREFS, JSON.stringify(s));
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
log2.warn("saveUserPrefs serialise failed", { error });
|
|
1065
|
+
}
|
|
1066
|
+
},
|
|
1067
|
+
patchUserPrefs(patch) {
|
|
1068
|
+
const current = persistence.loadUserPrefs();
|
|
1069
|
+
persistence.saveUserPrefs({ ...current, ...patch });
|
|
1070
|
+
},
|
|
1071
|
+
loadPanelOpen() {
|
|
1072
|
+
const raw = storage.get(KEY_PANEL_OPEN);
|
|
1073
|
+
if (raw === "1") return true;
|
|
1074
|
+
if (raw === "0") return false;
|
|
1075
|
+
return null;
|
|
1076
|
+
},
|
|
1077
|
+
savePanelOpen(open) {
|
|
1078
|
+
storage.set(KEY_PANEL_OPEN, open ? "1" : "0");
|
|
1079
|
+
},
|
|
1080
|
+
loadPanelSize() {
|
|
1081
|
+
const raw = storage.get(KEY_PANEL_SIZE);
|
|
1082
|
+
return raw === "normal" || raw === "expanded" || raw === "fullscreen" ? raw : null;
|
|
1083
|
+
},
|
|
1084
|
+
savePanelSize(size) {
|
|
1085
|
+
storage.set(KEY_PANEL_SIZE, size);
|
|
1086
|
+
},
|
|
1087
|
+
loadCalloutDismissed(currentText) {
|
|
1088
|
+
if (!currentText) return false;
|
|
1089
|
+
const dismissedText = storage.get(KEY_CALLOUT_DISMISSED);
|
|
1090
|
+
return !!dismissedText && dismissedText === currentText;
|
|
1091
|
+
},
|
|
1092
|
+
saveCalloutDismissed(currentText) {
|
|
1093
|
+
if (currentText) storage.set(KEY_CALLOUT_DISMISSED, currentText);
|
|
1094
|
+
else storage.remove(KEY_CALLOUT_DISMISSED);
|
|
1095
|
+
},
|
|
1096
|
+
loadSidebarCollapsed() {
|
|
1097
|
+
const raw = storage.get(KEY_SIDEBAR_COLLAPSED);
|
|
1098
|
+
if (raw === "1") return true;
|
|
1099
|
+
if (raw === "0") return false;
|
|
1100
|
+
return null;
|
|
1101
|
+
},
|
|
1102
|
+
saveSidebarCollapsed(collapsed) {
|
|
1103
|
+
storage.set(KEY_SIDEBAR_COLLAPSED, collapsed ? "1" : "0");
|
|
1104
|
+
},
|
|
1105
|
+
loadActiveModule() {
|
|
1106
|
+
return storage.get(KEY_ACTIVE_MODULE) || null;
|
|
1107
|
+
},
|
|
1108
|
+
saveActiveModule(id) {
|
|
1109
|
+
storage.set(KEY_ACTIVE_MODULE, id);
|
|
1110
|
+
},
|
|
1111
|
+
loadFormsDone() {
|
|
1112
|
+
const raw = storage.get(KEY_FORMS_DONE);
|
|
1113
|
+
if (!raw) return {};
|
|
1114
|
+
try {
|
|
1115
|
+
const parsed = JSON.parse(raw);
|
|
1116
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
log2.warn("loadFormsDone parse failed", { error });
|
|
1119
|
+
return {};
|
|
1120
|
+
}
|
|
1121
|
+
},
|
|
1122
|
+
saveFormDone(id) {
|
|
1123
|
+
try {
|
|
1124
|
+
const done = persistence.loadFormsDone();
|
|
1125
|
+
done[id] = Date.now();
|
|
1126
|
+
storage.set(KEY_FORMS_DONE, JSON.stringify(done));
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
log2.warn("saveFormDone serialise failed", { error });
|
|
1129
|
+
}
|
|
1130
|
+
},
|
|
1131
|
+
clearConversation() {
|
|
1132
|
+
storage.remove(KEY_CONVERSATION);
|
|
1133
|
+
}
|
|
1014
1134
|
};
|
|
1135
|
+
return persistence;
|
|
1136
|
+
}
|
|
1137
|
+
function isPlainObject(v) {
|
|
1138
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1015
1139
|
}
|
|
1016
1140
|
|
|
1017
1141
|
// src/styles/tokens.css
|
|
@@ -1038,7 +1162,7 @@ var FONT = true ? "Inter, system-ui, -apple-system, 'Segoe UI', sans-serif" : "I
|
|
|
1038
1162
|
var CSS_TEXT = [tokens_default, reset_default, panel_default, standalone_default, page_default].join("\n").replace(/__P__/g, PREFIX).replaceAll("__BRAND_ACCENT_TEXT__", ACCENT_TEXT).replaceAll("__BRAND_ACCENT__", ACCENT).replaceAll("__BRAND_RADIUS__", RADIUS).replaceAll("__BRAND_FONT__", FONT);
|
|
1039
1163
|
|
|
1040
1164
|
// src/shadow/mount.ts
|
|
1041
|
-
var
|
|
1165
|
+
var log3 = logger.scope("mount");
|
|
1042
1166
|
var sheetsByDocument = /* @__PURE__ */ new WeakMap();
|
|
1043
1167
|
var HOST_CSS = {
|
|
1044
1168
|
floating: "all: initial; position: fixed; z-index: 2147483600;",
|
|
@@ -1055,7 +1179,7 @@ function hostPositionForMode(mode) {
|
|
|
1055
1179
|
}
|
|
1056
1180
|
function createShadowMount(opts = {}) {
|
|
1057
1181
|
const position = opts.position ?? "flow";
|
|
1058
|
-
|
|
1182
|
+
log3.debug("createShadowMount", { position, hasTarget: !!opts.target, hasHost: !!opts.existingHost });
|
|
1059
1183
|
const tagName = `${BRAND.cssPrefix}-chat-host`;
|
|
1060
1184
|
if (typeof customElements !== "undefined" && !customElements.get(tagName)) {
|
|
1061
1185
|
customElements.define(tagName, class extends HTMLElement {
|
|
@@ -1139,6 +1263,44 @@ function applyHostAttributes(host, resolved) {
|
|
|
1139
1263
|
applySize(host, resolved.size);
|
|
1140
1264
|
}
|
|
1141
1265
|
|
|
1266
|
+
// src/core/boot.ts
|
|
1267
|
+
import { h, render as renderPreact } from "preact";
|
|
1268
|
+
|
|
1269
|
+
// src/ui/app.tsx
|
|
1270
|
+
import { useCallback as useCallback6, useEffect as useEffect17, useMemo as useMemo3, useRef as useRef9, useState as useState13 } from "preact/hooks";
|
|
1271
|
+
import { useComputed as useComputed7, useSignal as useSignal2 } from "@preact/signals";
|
|
1272
|
+
|
|
1273
|
+
// src/core/handshake-shape.ts
|
|
1274
|
+
function isPlainObject2(raw) {
|
|
1275
|
+
return !!raw && typeof raw === "object" && !Array.isArray(raw);
|
|
1276
|
+
}
|
|
1277
|
+
function isSiteConfigShape(raw) {
|
|
1278
|
+
return isPlainObject2(raw);
|
|
1279
|
+
}
|
|
1280
|
+
function isBlocksConfigShape(raw) {
|
|
1281
|
+
if (!isPlainObject2(raw)) return false;
|
|
1282
|
+
if (raw.navigation !== void 0 && !Array.isArray(raw.navigation)) return false;
|
|
1283
|
+
if (raw.linkCards !== void 0 && !Array.isArray(raw.linkCards)) return false;
|
|
1284
|
+
return true;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// src/core/host-commands.ts
|
|
1288
|
+
function commandEventName(cmd) {
|
|
1289
|
+
return `${BRAND.cssPrefix}:${cmd}`;
|
|
1290
|
+
}
|
|
1291
|
+
function bindHostCommands(host, handlers) {
|
|
1292
|
+
const entries = Object.entries(handlers);
|
|
1293
|
+
const wrapped = entries.map(([cmd, fn]) => {
|
|
1294
|
+
const event = commandEventName(cmd);
|
|
1295
|
+
const listener = (e) => fn(e.detail);
|
|
1296
|
+
host.addEventListener(event, listener);
|
|
1297
|
+
return [event, listener];
|
|
1298
|
+
});
|
|
1299
|
+
return () => {
|
|
1300
|
+
for (const [event, listener] of wrapped) host.removeEventListener(event, listener);
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1142
1304
|
// src/ui/host-mode.ts
|
|
1143
1305
|
function computeHostMode(configured, panelSize, isOpen) {
|
|
1144
1306
|
if (configured === "page") return "page";
|
|
@@ -1189,30 +1351,6 @@ function createAuth(opts) {
|
|
|
1189
1351
|
};
|
|
1190
1352
|
}
|
|
1191
1353
|
|
|
1192
|
-
// src/core/ids.ts
|
|
1193
|
-
function uuid7() {
|
|
1194
|
-
const ts = Date.now();
|
|
1195
|
-
const bytes = new Uint8Array(16);
|
|
1196
|
-
bytes[0] = Math.floor(ts / 2 ** 40) & 255;
|
|
1197
|
-
bytes[1] = Math.floor(ts / 2 ** 32) & 255;
|
|
1198
|
-
bytes[2] = ts >>> 24 & 255;
|
|
1199
|
-
bytes[3] = ts >>> 16 & 255;
|
|
1200
|
-
bytes[4] = ts >>> 8 & 255;
|
|
1201
|
-
bytes[5] = ts & 255;
|
|
1202
|
-
fillRandom(bytes.subarray(6));
|
|
1203
|
-
bytes[6] = bytes[6] & 15 | 112;
|
|
1204
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
1205
|
-
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1206
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1207
|
-
}
|
|
1208
|
-
function fillRandom(view) {
|
|
1209
|
-
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
1210
|
-
crypto.getRandomValues(view);
|
|
1211
|
-
return;
|
|
1212
|
-
}
|
|
1213
|
-
for (let i = 0; i < view.length; i++) view[i] = Math.floor(Math.random() * 256);
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
1354
|
// src/stream/types.ts
|
|
1217
1355
|
var StreamError = class extends Error {
|
|
1218
1356
|
constructor(message, code, status, cause) {
|
|
@@ -1225,7 +1363,7 @@ var StreamError = class extends Error {
|
|
|
1225
1363
|
};
|
|
1226
1364
|
|
|
1227
1365
|
// src/stream/parser.ts
|
|
1228
|
-
var
|
|
1366
|
+
var log4 = logger.scope("parser");
|
|
1229
1367
|
async function* parseChatStream(response, signal7) {
|
|
1230
1368
|
if (!response.ok) {
|
|
1231
1369
|
const text = await response.text().catch(() => "");
|
|
@@ -1277,10 +1415,10 @@ ${part}` : part;
|
|
|
1277
1415
|
if (!dataPayload || dataPayload === "[DONE]") return null;
|
|
1278
1416
|
try {
|
|
1279
1417
|
const chunk = JSON.parse(dataPayload);
|
|
1280
|
-
|
|
1418
|
+
log4.trace("chunk", { type: chunk.type, eventId });
|
|
1281
1419
|
return { chunk, eventId };
|
|
1282
1420
|
} catch (err) {
|
|
1283
|
-
|
|
1421
|
+
log4.trace("frame parse failed", { err, payload: dataPayload.slice(0, 200) });
|
|
1284
1422
|
return null;
|
|
1285
1423
|
}
|
|
1286
1424
|
}
|
|
@@ -1435,7 +1573,7 @@ function messageToWireParts(m) {
|
|
|
1435
1573
|
}
|
|
1436
1574
|
|
|
1437
1575
|
// src/stream/transport.ts
|
|
1438
|
-
var
|
|
1576
|
+
var log5 = logger.scope("transport");
|
|
1439
1577
|
var MAX_RESUME_ATTEMPTS = 3;
|
|
1440
1578
|
var RESUME_BACKOFF_MS = 400;
|
|
1441
1579
|
var CONTENT_CACHE_TTL_MS = 6e4;
|
|
@@ -1477,6 +1615,12 @@ var AgentTransport = class {
|
|
|
1477
1615
|
// always sees the visitor's latest context as they navigate.
|
|
1478
1616
|
__publicField(this, "userContext");
|
|
1479
1617
|
__publicField(this, "pageContext");
|
|
1618
|
+
// Active BCP-47 locale, carried as the envelope's `locale` on EVERY request
|
|
1619
|
+
// so the backend can localize replies AND the data module can return
|
|
1620
|
+
// localized content + forms. Refreshed via `setLocale` whenever the visitor
|
|
1621
|
+
// switches (no re-handshake needed). Resolved + clamped to the deployment's
|
|
1622
|
+
// availableLocales by `resolveOptions` before it reaches here.
|
|
1623
|
+
__publicField(this, "locale");
|
|
1480
1624
|
// ---- Data API (content / forms — module-help-ai-data-api) --------------
|
|
1481
1625
|
//
|
|
1482
1626
|
// The DATA surfaces live on `dataApiBaseUrl` (default `${baseUrl}/api/data`)
|
|
@@ -1502,6 +1646,15 @@ var AgentTransport = class {
|
|
|
1502
1646
|
this.userContext = userContext;
|
|
1503
1647
|
this.pageContext = pageContext;
|
|
1504
1648
|
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Update the active locale carried on every subsequent request. Call on
|
|
1651
|
+
* mount (before the first handshake) and whenever the user picks a new
|
|
1652
|
+
* locale — the next content / forms / message request reflects it with no
|
|
1653
|
+
* extra round-trip.
|
|
1654
|
+
*/
|
|
1655
|
+
setLocale(locale) {
|
|
1656
|
+
this.locale = locale;
|
|
1657
|
+
}
|
|
1505
1658
|
/**
|
|
1506
1659
|
* Seed the visitor + conversation identity from persistence BEFORE the first
|
|
1507
1660
|
* `handshake` resolves. A mount-time `resumeStream()` runs in parallel
|
|
@@ -1518,6 +1671,7 @@ var AgentTransport = class {
|
|
|
1518
1671
|
const out = {};
|
|
1519
1672
|
if (this.visitorId) out.visitorId = this.visitorId;
|
|
1520
1673
|
if (this.conversationId) out.conversationId = this.conversationId;
|
|
1674
|
+
if (this.locale) out.locale = this.locale;
|
|
1521
1675
|
const context = encodeContext(this.userContext, this.pageContext);
|
|
1522
1676
|
if (context) out[CONTEXT_PARAM] = context;
|
|
1523
1677
|
return out;
|
|
@@ -1545,13 +1699,13 @@ var AgentTransport = class {
|
|
|
1545
1699
|
}
|
|
1546
1700
|
/** One-shot runtime bootstrap. Called once after the widget mounts. */
|
|
1547
1701
|
async handshake(body) {
|
|
1548
|
-
|
|
1702
|
+
log5.debug("handshake \u2192", {
|
|
1549
1703
|
visitorId: body.visitorId,
|
|
1550
1704
|
conversationId: body.conversationId,
|
|
1551
1705
|
locale: body.locale
|
|
1552
1706
|
});
|
|
1553
1707
|
const res = await this.postJson(DEFAULT_PATHS.handshake, body, "handshake");
|
|
1554
|
-
|
|
1708
|
+
log5.debug("handshake \u2190", {
|
|
1555
1709
|
visitorId: res.visitorId,
|
|
1556
1710
|
conversationId: res.conversationId,
|
|
1557
1711
|
hasWelcome: !!res.welcome,
|
|
@@ -1559,7 +1713,7 @@ var AgentTransport = class {
|
|
|
1559
1713
|
rebind: res.rebind
|
|
1560
1714
|
});
|
|
1561
1715
|
if (!isHandshakeResponseShape(res)) {
|
|
1562
|
-
|
|
1716
|
+
log5.warn("handshake response did not match expected shape; using raw response", { response: res });
|
|
1563
1717
|
}
|
|
1564
1718
|
this.visitorId = res.visitorId ?? body.visitorId;
|
|
1565
1719
|
this.conversationId = res.conversationId;
|
|
@@ -1567,11 +1721,11 @@ var AgentTransport = class {
|
|
|
1567
1721
|
}
|
|
1568
1722
|
/** Upload a file attachment. Returns the `{id, url}` the backend assigns. */
|
|
1569
1723
|
async uploadFile(file) {
|
|
1570
|
-
|
|
1724
|
+
log5.debug("uploadFile \u2192", { name: file.name, size: file.size, type: file.type });
|
|
1571
1725
|
const fd = new FormData();
|
|
1572
1726
|
fd.append("file", file, file.name);
|
|
1573
1727
|
const res = await this.postForm(this.uploadPath, fd, "upload");
|
|
1574
|
-
|
|
1728
|
+
log5.debug("uploadFile \u2190", { id: res.id, url: res.url });
|
|
1575
1729
|
return res;
|
|
1576
1730
|
}
|
|
1577
1731
|
/**
|
|
@@ -1579,12 +1733,12 @@ var AgentTransport = class {
|
|
|
1579
1733
|
* Only used when `features.voice === 'server'`.
|
|
1580
1734
|
*/
|
|
1581
1735
|
async transcribe(audio, mimeType = "audio/webm") {
|
|
1582
|
-
|
|
1736
|
+
log5.debug("transcribe \u2192", { size: audio.size, mimeType });
|
|
1583
1737
|
const fd = new FormData();
|
|
1584
1738
|
fd.append("audio", audio, `voice.${extensionFor(mimeType)}`);
|
|
1585
1739
|
fd.append("mimeType", mimeType);
|
|
1586
1740
|
const res = await this.postForm(this.transcribePath, fd, "transcribe");
|
|
1587
|
-
|
|
1741
|
+
log5.debug("transcribe \u2190", { textLen: res.text.length });
|
|
1588
1742
|
return res;
|
|
1589
1743
|
}
|
|
1590
1744
|
async listConversations(params = {}) {
|
|
@@ -1592,14 +1746,14 @@ var AgentTransport = class {
|
|
|
1592
1746
|
if (params.visitorId) url.searchParams.set("visitorId", params.visitorId);
|
|
1593
1747
|
if (params.limit) url.searchParams.set("limit", String(params.limit));
|
|
1594
1748
|
if (params.cursor) url.searchParams.set("cursor", params.cursor);
|
|
1595
|
-
|
|
1749
|
+
log5.debug("listConversations \u2192", {
|
|
1596
1750
|
visitorId: params.visitorId ?? this.visitorId,
|
|
1597
1751
|
limit: params.limit,
|
|
1598
1752
|
cursor: params.cursor
|
|
1599
1753
|
});
|
|
1600
1754
|
const res = await this.getJson(url.toString(), "listConversations");
|
|
1601
1755
|
const conversations = res.conversations ?? [];
|
|
1602
|
-
|
|
1756
|
+
log5.debug("listConversations \u2190", { count: conversations.length, nextCursor: res.nextCursor });
|
|
1603
1757
|
return {
|
|
1604
1758
|
conversations: conversations.map((conversation) => ({
|
|
1605
1759
|
conversationId: conversation.conversationId,
|
|
@@ -1615,10 +1769,10 @@ var AgentTransport = class {
|
|
|
1615
1769
|
async loadConversation(conversationId) {
|
|
1616
1770
|
const url = new URL(this.url(DEFAULT_PATHS.listMessages));
|
|
1617
1771
|
url.searchParams.set("conversationId", conversationId);
|
|
1618
|
-
|
|
1772
|
+
log5.debug("loadConversation \u2192", { conversationId, visitorId: this.visitorId });
|
|
1619
1773
|
const res = await this.getJson(url.toString(), "loadConversation");
|
|
1620
1774
|
const messages = res.messages ?? [];
|
|
1621
|
-
|
|
1775
|
+
log5.debug("loadConversation \u2190", {
|
|
1622
1776
|
conversationId: res.conversationId,
|
|
1623
1777
|
canContinue: res.canContinue,
|
|
1624
1778
|
messageCount: messages.length,
|
|
@@ -1640,11 +1794,11 @@ var AgentTransport = class {
|
|
|
1640
1794
|
*/
|
|
1641
1795
|
async markRead(conversationId) {
|
|
1642
1796
|
if (!this.visitorId || !conversationId) return;
|
|
1643
|
-
|
|
1797
|
+
log5.debug("markRead \u2192", { conversationId, visitorId: this.visitorId });
|
|
1644
1798
|
try {
|
|
1645
1799
|
await this.postJson(DEFAULT_PATHS.markRead, { conversationId }, "markRead");
|
|
1646
1800
|
} catch (err) {
|
|
1647
|
-
|
|
1801
|
+
log5.debug("markRead failed (non-fatal)", { err });
|
|
1648
1802
|
}
|
|
1649
1803
|
}
|
|
1650
1804
|
/** Fetch content rows by filter. `GET /pai/content` (data-API), TTL-cached. */
|
|
@@ -1652,7 +1806,7 @@ var AgentTransport = class {
|
|
|
1652
1806
|
const key = JSON.stringify(query, Object.keys(query).toSorted());
|
|
1653
1807
|
const cached = this.contentCache.get(key);
|
|
1654
1808
|
if (cached && Date.now() - cached.at < CONTENT_CACHE_TTL_MS) {
|
|
1655
|
-
|
|
1809
|
+
log5.debug("listContent \u2192 cache", query);
|
|
1656
1810
|
return cached.result;
|
|
1657
1811
|
}
|
|
1658
1812
|
const result = this.fetchContent(query).catch((err) => {
|
|
@@ -1668,10 +1822,10 @@ var AgentTransport = class {
|
|
|
1668
1822
|
if (value === void 0) continue;
|
|
1669
1823
|
url.searchParams.set(key, Array.isArray(value) ? value.join(",") : String(value));
|
|
1670
1824
|
}
|
|
1671
|
-
|
|
1825
|
+
log5.debug("listContent \u2192", query);
|
|
1672
1826
|
const res = await this.getJson(url.toString(), "listContent");
|
|
1673
1827
|
const items = res.items ?? [];
|
|
1674
|
-
|
|
1828
|
+
log5.debug("listContent \u2190", { count: items.length, total: res.pagination?.total });
|
|
1675
1829
|
return { ...res, items };
|
|
1676
1830
|
}
|
|
1677
1831
|
/**
|
|
@@ -1689,21 +1843,21 @@ var AgentTransport = class {
|
|
|
1689
1843
|
async bootstrap(conversationId) {
|
|
1690
1844
|
const url = new URL(this.dataUrl(DEFAULT_PATHS.bootstrap));
|
|
1691
1845
|
if (conversationId) url.searchParams.set("conversationId", conversationId);
|
|
1692
|
-
|
|
1846
|
+
log5.debug("bootstrap \u2192", { conversationId: conversationId ?? this.conversationId });
|
|
1693
1847
|
const res = await this.getJson(url.toString(), "bootstrap");
|
|
1694
1848
|
const forms = res.forms ?? [];
|
|
1695
1849
|
const formSubmissions = res.formSubmissions ?? [];
|
|
1696
|
-
|
|
1850
|
+
log5.debug("bootstrap \u2190", { forms: forms.length, formSubmissions: formSubmissions.length });
|
|
1697
1851
|
return { forms, formSubmissions };
|
|
1698
1852
|
}
|
|
1699
1853
|
async saveUserPrefs(userPrefs) {
|
|
1700
|
-
|
|
1854
|
+
log5.debug("saveUserPrefs \u2192", {
|
|
1701
1855
|
visitorId: this.visitorId,
|
|
1702
1856
|
conversationId: this.conversationId,
|
|
1703
1857
|
settingsKeys: Object.keys(userPrefs)
|
|
1704
1858
|
});
|
|
1705
1859
|
const res = await this.postJson(DEFAULT_PATHS.updateSettings, { userPrefs }, "saveUserPrefs");
|
|
1706
|
-
|
|
1860
|
+
log5.debug("saveUserPrefs \u2190", { ok: res.ok });
|
|
1707
1861
|
return res;
|
|
1708
1862
|
}
|
|
1709
1863
|
get uploadPath() {
|
|
@@ -1724,15 +1878,15 @@ var AgentTransport = class {
|
|
|
1724
1878
|
* that doesn't implement the endpoint) is non-fatal, so callers don't await.
|
|
1725
1879
|
*/
|
|
1726
1880
|
async submitForm(body) {
|
|
1727
|
-
|
|
1881
|
+
log5.debug("submitForm \u2192", { formId: body.formId, trigger: body.trigger, fields: Object.keys(body.values).length });
|
|
1728
1882
|
try {
|
|
1729
1883
|
await this.postJson(this.dataUrl(this.submitFormPath), body, "submitForm");
|
|
1730
1884
|
} catch (err) {
|
|
1731
|
-
|
|
1885
|
+
log5.debug("submitForm failed (non-fatal)", { err });
|
|
1732
1886
|
}
|
|
1733
1887
|
}
|
|
1734
1888
|
sendMessage(body) {
|
|
1735
|
-
|
|
1889
|
+
log5.debug("message \u2192", {
|
|
1736
1890
|
conversationId: body.conversationId,
|
|
1737
1891
|
visitorId: body.visitorId,
|
|
1738
1892
|
messageCount: body.messages.length,
|
|
@@ -1812,17 +1966,17 @@ var AgentTransport = class {
|
|
|
1812
1966
|
});
|
|
1813
1967
|
} catch (err) {
|
|
1814
1968
|
if (ctrl.signal.aborted) return;
|
|
1815
|
-
|
|
1969
|
+
log5.debug("resume fetch failed \u2014 retrying", { attempt, err });
|
|
1816
1970
|
continue;
|
|
1817
1971
|
}
|
|
1818
1972
|
if (res.status === 204 || !res.ok) {
|
|
1819
|
-
|
|
1973
|
+
log5.debug("resume \u2192 no in-flight stream", { status: res.status });
|
|
1820
1974
|
return;
|
|
1821
1975
|
}
|
|
1822
1976
|
const before = seenIds.size;
|
|
1823
1977
|
if (yield* this.drain(parseChatStream(res, ctrl.signal), seenIds, ctrl)) return;
|
|
1824
1978
|
if (seenIds.size === before) {
|
|
1825
|
-
|
|
1979
|
+
log5.debug("resume \u2192 stream closed with no new events; nothing to resume");
|
|
1826
1980
|
return;
|
|
1827
1981
|
}
|
|
1828
1982
|
}
|
|
@@ -1849,14 +2003,14 @@ var AgentTransport = class {
|
|
|
1849
2003
|
} catch (err) {
|
|
1850
2004
|
if (ctrl.signal.aborted) return true;
|
|
1851
2005
|
if (err instanceof StreamError && err.status !== void 0) throw err;
|
|
1852
|
-
|
|
2006
|
+
log5.debug("stream segment dropped", { err });
|
|
1853
2007
|
}
|
|
1854
2008
|
return false;
|
|
1855
2009
|
}
|
|
1856
2010
|
/** Abort + fire-and-forget POST to `/pai/cancel-stream`. Idempotent. */
|
|
1857
2011
|
cancelStream(ctrl, conversationId) {
|
|
1858
2012
|
if (ctrl.signal.aborted) return;
|
|
1859
|
-
|
|
2013
|
+
log5.debug("cancel stream", { conversationId, visitorId: this.visitorId });
|
|
1860
2014
|
ctrl.abort();
|
|
1861
2015
|
if (!this.visitorId || !conversationId) return;
|
|
1862
2016
|
this.fetchImpl(this.url(DEFAULT_PATHS.cancelStream), {
|
|
@@ -1865,7 +2019,7 @@ var AgentTransport = class {
|
|
|
1865
2019
|
headers: { "content-type": "application/json", ...this.opts.auth.headers() },
|
|
1866
2020
|
body: JSON.stringify(this.withEnvelope({})),
|
|
1867
2021
|
keepalive: true
|
|
1868
|
-
}).catch((err) =>
|
|
2022
|
+
}).catch((err) => log5.debug("cancel POST failed (non-fatal)", { err }));
|
|
1869
2023
|
}
|
|
1870
2024
|
// ---- Low-level fetch helpers ------------------------------------------
|
|
1871
2025
|
async *openMessageStream(body, signal7) {
|
|
@@ -1947,13 +2101,13 @@ var AgentTransport = class {
|
|
|
1947
2101
|
if (attempt >= MAX_REQUEST_RETRIES) {
|
|
1948
2102
|
throw new StreamError(`${label} failed: network error`, "network", void 0, err);
|
|
1949
2103
|
}
|
|
1950
|
-
|
|
2104
|
+
log5.debug("request network error \u2014 retrying", { label, attempt });
|
|
1951
2105
|
await sleep(backoffMs(attempt));
|
|
1952
2106
|
continue;
|
|
1953
2107
|
}
|
|
1954
2108
|
if (res.ok || !RETRYABLE_STATUS.has(res.status) || attempt >= MAX_REQUEST_RETRIES) return res;
|
|
1955
2109
|
const wait = retryAfterMs(res.headers) ?? backoffMs(attempt);
|
|
1956
|
-
|
|
2110
|
+
log5.debug("request retryable status \u2014 retrying", { label, status: res.status, attempt, wait });
|
|
1957
2111
|
await sleep(wait);
|
|
1958
2112
|
}
|
|
1959
2113
|
}
|
|
@@ -2148,7 +2302,7 @@ var TRIGGER = {
|
|
|
2148
2302
|
};
|
|
2149
2303
|
|
|
2150
2304
|
// src/stream/reducer.ts
|
|
2151
|
-
var
|
|
2305
|
+
var log6 = logger.scope("reducer");
|
|
2152
2306
|
var StreamReducer = class {
|
|
2153
2307
|
constructor(messagesSig) {
|
|
2154
2308
|
__publicField(this, "messagesSig", messagesSig);
|
|
@@ -2170,13 +2324,13 @@ var StreamReducer = class {
|
|
|
2170
2324
|
case "finish-step":
|
|
2171
2325
|
return;
|
|
2172
2326
|
case "finish":
|
|
2173
|
-
|
|
2327
|
+
log6.debug("finish", { reason: chunk.finishReason, canContinue: chunk.canContinue });
|
|
2174
2328
|
m.status = "complete";
|
|
2175
2329
|
if (chunk.finishReason) m.finishReason = chunk.finishReason;
|
|
2176
2330
|
this.bumpDone(m);
|
|
2177
2331
|
return;
|
|
2178
2332
|
case "error":
|
|
2179
|
-
|
|
2333
|
+
log6.warn("stream error chunk", { errorText: chunk.errorText });
|
|
2180
2334
|
m.status = "error";
|
|
2181
2335
|
m.errorText = chunk.errorText;
|
|
2182
2336
|
this.bumpDone(m);
|
|
@@ -2305,131 +2459,6 @@ function applyTool(m, chunk) {
|
|
|
2305
2459
|
}
|
|
2306
2460
|
}
|
|
2307
2461
|
|
|
2308
|
-
// src/core/persistence.ts
|
|
2309
|
-
var log6 = logger.scope("persistence");
|
|
2310
|
-
function createPersistence(widgetId, storage = defaultStorage) {
|
|
2311
|
-
const prefix = `${BRAND.cssPrefix}.${widgetId}`;
|
|
2312
|
-
const KEY_VISITOR = `${prefix}.visitorId`;
|
|
2313
|
-
const KEY_CONVERSATION = `${prefix}.conversationId`;
|
|
2314
|
-
const KEY_USER_PREFS = `${prefix}.userPrefs`;
|
|
2315
|
-
const KEY_PANEL_OPEN = `${prefix}.panelOpen`;
|
|
2316
|
-
const KEY_PANEL_SIZE = `${prefix}.panelSize`;
|
|
2317
|
-
const KEY_CALLOUT_DISMISSED = `${prefix}.calloutDismissed`;
|
|
2318
|
-
const KEY_SIDEBAR_COLLAPSED = `${prefix}.sidebarCollapsed`;
|
|
2319
|
-
const KEY_ACTIVE_MODULE = `${prefix}.activeModule`;
|
|
2320
|
-
const KEY_FORMS_DONE = `${prefix}.formsDone`;
|
|
2321
|
-
const persistence = {
|
|
2322
|
-
getVisitorId() {
|
|
2323
|
-
const existing = storage.get(KEY_VISITOR);
|
|
2324
|
-
if (existing) return existing;
|
|
2325
|
-
const minted = uuid7();
|
|
2326
|
-
storage.set(KEY_VISITOR, minted);
|
|
2327
|
-
return minted;
|
|
2328
|
-
},
|
|
2329
|
-
setVisitorId(id) {
|
|
2330
|
-
log6.debug("setVisitorId (rebind)", { id, widgetId });
|
|
2331
|
-
storage.set(KEY_VISITOR, id);
|
|
2332
|
-
},
|
|
2333
|
-
loadConversationId() {
|
|
2334
|
-
return storage.get(KEY_CONVERSATION) ?? void 0;
|
|
2335
|
-
},
|
|
2336
|
-
saveConversationId(id) {
|
|
2337
|
-
log6.trace("saveConversationId", { id, widgetId });
|
|
2338
|
-
if (id) storage.set(KEY_CONVERSATION, id);
|
|
2339
|
-
else storage.remove(KEY_CONVERSATION);
|
|
2340
|
-
},
|
|
2341
|
-
loadUserPrefs() {
|
|
2342
|
-
const raw = storage.get(KEY_USER_PREFS);
|
|
2343
|
-
if (!raw) return {};
|
|
2344
|
-
try {
|
|
2345
|
-
const parsed = JSON.parse(raw);
|
|
2346
|
-
return isPlainObject2(parsed) ? parsed : {};
|
|
2347
|
-
} catch (error) {
|
|
2348
|
-
log6.warn("loadUserPrefs parse failed", { error });
|
|
2349
|
-
return {};
|
|
2350
|
-
}
|
|
2351
|
-
},
|
|
2352
|
-
saveUserPrefs(s) {
|
|
2353
|
-
try {
|
|
2354
|
-
storage.set(KEY_USER_PREFS, JSON.stringify(s));
|
|
2355
|
-
} catch (error) {
|
|
2356
|
-
log6.warn("saveUserPrefs serialise failed", { error });
|
|
2357
|
-
}
|
|
2358
|
-
},
|
|
2359
|
-
patchUserPrefs(patch) {
|
|
2360
|
-
const current = persistence.loadUserPrefs();
|
|
2361
|
-
persistence.saveUserPrefs({ ...current, ...patch });
|
|
2362
|
-
},
|
|
2363
|
-
loadPanelOpen() {
|
|
2364
|
-
const raw = storage.get(KEY_PANEL_OPEN);
|
|
2365
|
-
if (raw === "1") return true;
|
|
2366
|
-
if (raw === "0") return false;
|
|
2367
|
-
return null;
|
|
2368
|
-
},
|
|
2369
|
-
savePanelOpen(open) {
|
|
2370
|
-
storage.set(KEY_PANEL_OPEN, open ? "1" : "0");
|
|
2371
|
-
},
|
|
2372
|
-
loadPanelSize() {
|
|
2373
|
-
const raw = storage.get(KEY_PANEL_SIZE);
|
|
2374
|
-
return raw === "normal" || raw === "expanded" || raw === "fullscreen" ? raw : null;
|
|
2375
|
-
},
|
|
2376
|
-
savePanelSize(size) {
|
|
2377
|
-
storage.set(KEY_PANEL_SIZE, size);
|
|
2378
|
-
},
|
|
2379
|
-
loadCalloutDismissed(currentText) {
|
|
2380
|
-
if (!currentText) return false;
|
|
2381
|
-
const dismissedText = storage.get(KEY_CALLOUT_DISMISSED);
|
|
2382
|
-
return !!dismissedText && dismissedText === currentText;
|
|
2383
|
-
},
|
|
2384
|
-
saveCalloutDismissed(currentText) {
|
|
2385
|
-
if (currentText) storage.set(KEY_CALLOUT_DISMISSED, currentText);
|
|
2386
|
-
else storage.remove(KEY_CALLOUT_DISMISSED);
|
|
2387
|
-
},
|
|
2388
|
-
loadSidebarCollapsed() {
|
|
2389
|
-
const raw = storage.get(KEY_SIDEBAR_COLLAPSED);
|
|
2390
|
-
if (raw === "1") return true;
|
|
2391
|
-
if (raw === "0") return false;
|
|
2392
|
-
return null;
|
|
2393
|
-
},
|
|
2394
|
-
saveSidebarCollapsed(collapsed) {
|
|
2395
|
-
storage.set(KEY_SIDEBAR_COLLAPSED, collapsed ? "1" : "0");
|
|
2396
|
-
},
|
|
2397
|
-
loadActiveModule() {
|
|
2398
|
-
return storage.get(KEY_ACTIVE_MODULE) || null;
|
|
2399
|
-
},
|
|
2400
|
-
saveActiveModule(id) {
|
|
2401
|
-
storage.set(KEY_ACTIVE_MODULE, id);
|
|
2402
|
-
},
|
|
2403
|
-
loadFormsDone() {
|
|
2404
|
-
const raw = storage.get(KEY_FORMS_DONE);
|
|
2405
|
-
if (!raw) return {};
|
|
2406
|
-
try {
|
|
2407
|
-
const parsed = JSON.parse(raw);
|
|
2408
|
-
return isPlainObject2(parsed) ? parsed : {};
|
|
2409
|
-
} catch (error) {
|
|
2410
|
-
log6.warn("loadFormsDone parse failed", { error });
|
|
2411
|
-
return {};
|
|
2412
|
-
}
|
|
2413
|
-
},
|
|
2414
|
-
saveFormDone(id) {
|
|
2415
|
-
try {
|
|
2416
|
-
const done = persistence.loadFormsDone();
|
|
2417
|
-
done[id] = Date.now();
|
|
2418
|
-
storage.set(KEY_FORMS_DONE, JSON.stringify(done));
|
|
2419
|
-
} catch (error) {
|
|
2420
|
-
log6.warn("saveFormDone serialise failed", { error });
|
|
2421
|
-
}
|
|
2422
|
-
},
|
|
2423
|
-
clearConversation() {
|
|
2424
|
-
storage.remove(KEY_CONVERSATION);
|
|
2425
|
-
}
|
|
2426
|
-
};
|
|
2427
|
-
return persistence;
|
|
2428
|
-
}
|
|
2429
|
-
function isPlainObject2(v) {
|
|
2430
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
2462
|
// src/core/feedback/index.ts
|
|
2434
2463
|
import { signal as signal3 } from "@preact/signals";
|
|
2435
2464
|
|
|
@@ -6725,7 +6754,10 @@ function App({ options, hostElement, bus }) {
|
|
|
6725
6754
|
visitorId,
|
|
6726
6755
|
conversationId,
|
|
6727
6756
|
userPrefs: persistence.loadUserPrefs(),
|
|
6728
|
-
locale
|
|
6757
|
+
// The visitor's ACTIVE locale (persisted pick, else auto-detected) —
|
|
6758
|
+
// not the config baseline — so the handshake learns what the visitor
|
|
6759
|
+
// wants. The response's availableLocales then clamps it for the rest.
|
|
6760
|
+
locale: activeLocale
|
|
6729
6761
|
});
|
|
6730
6762
|
} catch (err) {
|
|
6731
6763
|
if (isStale()) return;
|
|
@@ -6787,7 +6819,18 @@ function App({ options, hostElement, bus }) {
|
|
|
6787
6819
|
setConversationReady(true);
|
|
6788
6820
|
}
|
|
6789
6821
|
},
|
|
6790
|
-
[
|
|
6822
|
+
[
|
|
6823
|
+
transport,
|
|
6824
|
+
visitorId,
|
|
6825
|
+
persistence,
|
|
6826
|
+
options,
|
|
6827
|
+
bus,
|
|
6828
|
+
conversationIdSig,
|
|
6829
|
+
feedback,
|
|
6830
|
+
playWelcome,
|
|
6831
|
+
loadThread,
|
|
6832
|
+
activeLocale
|
|
6833
|
+
]
|
|
6791
6834
|
);
|
|
6792
6835
|
const userSig = JSON.stringify(options.userContext ?? null);
|
|
6793
6836
|
const pageSig = JSON.stringify(options.pageContext ?? null);
|
|
@@ -6796,6 +6839,13 @@ function App({ options, hostElement, bus }) {
|
|
|
6796
6839
|
const merged = Object.keys(formContext).length ? { ...formContext, ...options.userContext } : options.userContext;
|
|
6797
6840
|
transport.setContext(merged, toWirePageContext(options.pageContext));
|
|
6798
6841
|
}, [transport, userSig, pageSig, formCtxSig]);
|
|
6842
|
+
const effectiveLocale = clampLocale(activeLocale, options.availableLocales, options.defaultLocale);
|
|
6843
|
+
useEffect17(() => {
|
|
6844
|
+
transport.setLocale(effectiveLocale);
|
|
6845
|
+
}, [transport, effectiveLocale]);
|
|
6846
|
+
useEffect17(() => {
|
|
6847
|
+
if (effectiveLocale !== activeLocale) setActiveLocale(effectiveLocale);
|
|
6848
|
+
}, [effectiveLocale, activeLocale]);
|
|
6799
6849
|
const runResume = useCallback6(
|
|
6800
6850
|
async (handle) => {
|
|
6801
6851
|
let bubble = null;
|
|
@@ -7446,51 +7496,21 @@ function emitMessage(bus, options, role, text) {
|
|
|
7446
7496
|
options.onMessage?.(payload);
|
|
7447
7497
|
}
|
|
7448
7498
|
|
|
7449
|
-
// src/index.ts
|
|
7450
|
-
import { h, render } from "preact";
|
|
7451
|
-
|
|
7452
7499
|
// src/ui/error-boundary.tsx
|
|
7453
7500
|
import { Component } from "preact";
|
|
7454
7501
|
var log17 = logger.scope("error-boundary");
|
|
7455
|
-
|
|
7456
|
-
// src/shadow/iframe-mount.ts
|
|
7457
|
-
var log18 = logger.scope("iframe");
|
|
7458
|
-
|
|
7459
|
-
// src/core/events.ts
|
|
7460
|
-
var EventBus = class {
|
|
7502
|
+
var ErrorBoundary = class extends Component {
|
|
7461
7503
|
constructor() {
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
/**
|
|
7465
|
-
* Register a listener for `name`.
|
|
7466
|
-
* @returns an `off` function that removes this listener.
|
|
7467
|
-
*/
|
|
7468
|
-
on(name, fn) {
|
|
7469
|
-
let set = this.handlers.get(name);
|
|
7470
|
-
if (!set) {
|
|
7471
|
-
set = /* @__PURE__ */ new Set();
|
|
7472
|
-
this.handlers.set(name, set);
|
|
7473
|
-
}
|
|
7474
|
-
set.add(fn);
|
|
7475
|
-
return () => this.handlers.get(name)?.delete(fn);
|
|
7504
|
+
super(...arguments);
|
|
7505
|
+
__publicField(this, "state", { crashed: false });
|
|
7476
7506
|
}
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
emit(name, payload) {
|
|
7482
|
-
const set = this.handlers.get(name);
|
|
7483
|
-
if (!set) return;
|
|
7484
|
-
for (const fn of set) {
|
|
7485
|
-
try {
|
|
7486
|
-
fn(payload);
|
|
7487
|
-
} catch {
|
|
7488
|
-
}
|
|
7489
|
-
}
|
|
7507
|
+
componentDidCatch(error, info) {
|
|
7508
|
+
log17.error("widget render crashed", { error, componentStack: info?.componentStack });
|
|
7509
|
+
this.setState({ crashed: true });
|
|
7510
|
+
this.props.onError(error, info?.componentStack);
|
|
7490
7511
|
}
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
this.handlers.clear();
|
|
7512
|
+
render() {
|
|
7513
|
+
return this.state.crashed ? this.props.fallback : this.props.children;
|
|
7494
7514
|
}
|
|
7495
7515
|
};
|
|
7496
7516
|
|
|
@@ -7609,10 +7629,60 @@ function createTracker(bus, deps) {
|
|
|
7609
7629
|
};
|
|
7610
7630
|
}
|
|
7611
7631
|
|
|
7612
|
-
// src/core/
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7632
|
+
// src/core/boot.ts
|
|
7633
|
+
function createWidgetRuntime(params) {
|
|
7634
|
+
const { bus, host, appRoot, persistence, reportError } = params;
|
|
7635
|
+
let userRaw = params.initialRaw;
|
|
7636
|
+
let serverConfig;
|
|
7637
|
+
let currentOptions = resolveOptions(userRaw);
|
|
7638
|
+
let firstRender = true;
|
|
7639
|
+
const recompute = () => {
|
|
7640
|
+
currentOptions = resolveOptions(serverConfig ? mergeServerConfig(userRaw, serverConfig) : userRaw);
|
|
7641
|
+
};
|
|
7642
|
+
const applyHostStyling = () => {
|
|
7643
|
+
applyHostPosition(host, hostPositionForMode(currentOptions.mode));
|
|
7644
|
+
const cachedThemeMode = persistence.loadUserPrefs().themeMode;
|
|
7645
|
+
applyHostAttributes(host, cachedThemeMode ? { ...currentOptions, themeMode: cachedThemeMode } : currentOptions);
|
|
7646
|
+
if (firstRender) {
|
|
7647
|
+
applyMode(host, resolveInitialHostMode(currentOptions));
|
|
7648
|
+
firstRender = false;
|
|
7649
|
+
}
|
|
7650
|
+
};
|
|
7651
|
+
const render2 = () => {
|
|
7652
|
+
applyHostStyling();
|
|
7653
|
+
renderPreact(
|
|
7654
|
+
h(ErrorBoundary, { onError: reportError }, h(App, { bus, hostElement: host, options: currentOptions })),
|
|
7655
|
+
appRoot
|
|
7656
|
+
);
|
|
7657
|
+
};
|
|
7658
|
+
bus.on("handshake", (response) => {
|
|
7659
|
+
if (!response.config) return;
|
|
7660
|
+
serverConfig = response.config;
|
|
7661
|
+
recompute();
|
|
7662
|
+
render2();
|
|
7663
|
+
});
|
|
7664
|
+
const untrack = createTracker(bus, {
|
|
7665
|
+
getOptions: () => currentOptions,
|
|
7666
|
+
getVisitorId: () => persistence.getVisitorId(),
|
|
7667
|
+
getConversationId: () => persistence.loadConversationId()
|
|
7668
|
+
});
|
|
7669
|
+
return {
|
|
7670
|
+
getOptions: () => currentOptions,
|
|
7671
|
+
getUserOptions: () => userRaw,
|
|
7672
|
+
render: render2,
|
|
7673
|
+
setUserOptions: (raw) => {
|
|
7674
|
+
userRaw = raw;
|
|
7675
|
+
recompute();
|
|
7676
|
+
render2();
|
|
7677
|
+
},
|
|
7678
|
+
patchUserOptions: (patch) => {
|
|
7679
|
+
userRaw = { ...userRaw, ...patch };
|
|
7680
|
+
recompute();
|
|
7681
|
+
render2();
|
|
7682
|
+
},
|
|
7683
|
+
destroy: () => untrack()
|
|
7684
|
+
};
|
|
7685
|
+
}
|
|
7616
7686
|
function resolveInitialHostMode(options) {
|
|
7617
7687
|
if (options.mode === "standalone") return "standalone";
|
|
7618
7688
|
if (options.mode === "inline") return "inline";
|
|
@@ -7626,35 +7696,52 @@ function resolveInitialHostMode(options) {
|
|
|
7626
7696
|
return computeHostMode(options.mode, panelSize, panelOpen);
|
|
7627
7697
|
}
|
|
7628
7698
|
|
|
7699
|
+
// src/core/events.ts
|
|
7700
|
+
var EventBus = class {
|
|
7701
|
+
constructor() {
|
|
7702
|
+
__publicField(this, "handlers", /* @__PURE__ */ new Map());
|
|
7703
|
+
}
|
|
7704
|
+
/**
|
|
7705
|
+
* Register a listener for `name`.
|
|
7706
|
+
* @returns an `off` function that removes this listener.
|
|
7707
|
+
*/
|
|
7708
|
+
on(name, fn) {
|
|
7709
|
+
let set = this.handlers.get(name);
|
|
7710
|
+
if (!set) {
|
|
7711
|
+
set = /* @__PURE__ */ new Set();
|
|
7712
|
+
this.handlers.set(name, set);
|
|
7713
|
+
}
|
|
7714
|
+
set.add(fn);
|
|
7715
|
+
return () => this.handlers.get(name)?.delete(fn);
|
|
7716
|
+
}
|
|
7717
|
+
/**
|
|
7718
|
+
* Dispatch an event synchronously to all listeners.
|
|
7719
|
+
* Listener errors are swallowed — the widget must not crash on user callbacks.
|
|
7720
|
+
*/
|
|
7721
|
+
emit(name, payload) {
|
|
7722
|
+
const set = this.handlers.get(name);
|
|
7723
|
+
if (!set) return;
|
|
7724
|
+
for (const fn of set) {
|
|
7725
|
+
try {
|
|
7726
|
+
fn(payload);
|
|
7727
|
+
} catch {
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7731
|
+
/** Remove every listener. Called by the instance `destroy()`. */
|
|
7732
|
+
clear() {
|
|
7733
|
+
this.handlers.clear();
|
|
7734
|
+
}
|
|
7735
|
+
};
|
|
7736
|
+
|
|
7629
7737
|
// src/web-component.ts
|
|
7630
7738
|
var ChatElement = class extends HTMLElement {
|
|
7631
7739
|
constructor() {
|
|
7632
7740
|
super(...arguments);
|
|
7633
7741
|
__publicField(this, "bus", new EventBus());
|
|
7634
7742
|
__publicField(this, "mountResult", null);
|
|
7635
|
-
/**
|
|
7636
|
-
|
|
7637
|
-
* HTML attributes; gets server-side settings merged in on the
|
|
7638
|
-
* `handshake` event so subsequent re-renders pick up the server's
|
|
7639
|
-
* mode / theme / launcher / etc.
|
|
7640
|
-
*
|
|
7641
|
-
* Mirrors `index.ts`'s `rawOptions` so both entry points share the
|
|
7642
|
-
* same user-wins-per-field merge policy.
|
|
7643
|
-
*/
|
|
7644
|
-
__publicField(this, "rawOptions", {});
|
|
7645
|
-
/**
|
|
7646
|
-
* Latest resolved options — refreshed every `renderApp()` so the usage
|
|
7647
|
-
* tracker (created once in `boot`) reads live, post-handshake-merge
|
|
7648
|
-
* values (`tracking.enabled` / `endpoint` / `token`).
|
|
7649
|
-
*
|
|
7650
|
-
* This path is independent of `mount()` (it supports N elements per page
|
|
7651
|
-
* without a shared `widgetId` registry), so anything `mount()` wires onto
|
|
7652
|
-
* the bus must be wired here too — the tracker is the current example;
|
|
7653
|
-
* keep them in lockstep.
|
|
7654
|
-
*/
|
|
7655
|
-
__publicField(this, "resolved", null);
|
|
7656
|
-
/** Tracker unsubscribe — released on disconnect. */
|
|
7657
|
-
__publicField(this, "untrack", null);
|
|
7743
|
+
/** Live widget composition — null between disconnect and the next connect. */
|
|
7744
|
+
__publicField(this, "runtime", null);
|
|
7658
7745
|
}
|
|
7659
7746
|
static get observedAttributes() {
|
|
7660
7747
|
return REACTIVE_ATTRS;
|
|
@@ -7664,50 +7751,37 @@ var ChatElement = class extends HTMLElement {
|
|
|
7664
7751
|
}
|
|
7665
7752
|
disconnectedCallback() {
|
|
7666
7753
|
if (!this.mountResult) return;
|
|
7667
|
-
this.
|
|
7668
|
-
this.
|
|
7669
|
-
|
|
7754
|
+
this.runtime?.destroy();
|
|
7755
|
+
this.runtime = null;
|
|
7756
|
+
render(null, this.mountResult.appRoot);
|
|
7670
7757
|
this.bus.clear();
|
|
7671
7758
|
}
|
|
7672
7759
|
attributeChangedCallback() {
|
|
7673
7760
|
if (this.isConnected) this.boot();
|
|
7674
7761
|
}
|
|
7675
7762
|
boot() {
|
|
7676
|
-
|
|
7763
|
+
const rawOptions = parseAttributes((name) => this.getAttribute(name));
|
|
7764
|
+
if (this.runtime) {
|
|
7765
|
+
this.runtime.setUserOptions(rawOptions);
|
|
7766
|
+
return;
|
|
7767
|
+
}
|
|
7768
|
+
const resolved = resolveOptions(rawOptions);
|
|
7677
7769
|
if (!this.mountResult) {
|
|
7678
|
-
const resolved = resolveOptions(this.rawOptions);
|
|
7679
7770
|
this.mountResult = createShadowMount({
|
|
7680
7771
|
existingHost: this,
|
|
7681
7772
|
position: hostPositionForMode(resolved.mode)
|
|
7682
7773
|
});
|
|
7683
|
-
this.bus.on("handshake", (response) => {
|
|
7684
|
-
if (!response.config) return;
|
|
7685
|
-
this.rawOptions = mergeServerConfig(this.rawOptions, response.config);
|
|
7686
|
-
this.renderApp();
|
|
7687
|
-
});
|
|
7688
|
-
const persistence = createPersistence(resolved.widgetId, resolved.storage);
|
|
7689
|
-
this.untrack = createTracker(this.bus, {
|
|
7690
|
-
getOptions: () => this.resolved ?? resolved,
|
|
7691
|
-
getVisitorId: () => persistence.getVisitorId(),
|
|
7692
|
-
getConversationId: () => persistence.loadConversationId()
|
|
7693
|
-
});
|
|
7694
7774
|
}
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
this.
|
|
7705
|
-
const host = this.mountResult.hostElement;
|
|
7706
|
-
applyHostPosition(host, hostPositionForMode(resolved.mode));
|
|
7707
|
-
const cachedThemeMode = createPersistence(resolved.widgetId, resolved.storage).loadUserPrefs().themeMode;
|
|
7708
|
-
applyHostAttributes(host, cachedThemeMode ? { ...resolved, themeMode: cachedThemeMode } : resolved);
|
|
7709
|
-
applyMode(host, resolveInitialHostMode(resolved));
|
|
7710
|
-
render2(h2(App, { bus: this.bus, hostElement: host, options: resolved }), this.mountResult.appRoot);
|
|
7775
|
+
const persistence = createPersistence(resolved.widgetId, resolved.storage);
|
|
7776
|
+
this.runtime = createWidgetRuntime({
|
|
7777
|
+
bus: this.bus,
|
|
7778
|
+
host: this.mountResult.hostElement,
|
|
7779
|
+
appRoot: this.mountResult.appRoot,
|
|
7780
|
+
persistence,
|
|
7781
|
+
initialRaw: rawOptions,
|
|
7782
|
+
reportError: (error) => this.bus.emit("error", error)
|
|
7783
|
+
});
|
|
7784
|
+
this.runtime.render();
|
|
7711
7785
|
}
|
|
7712
7786
|
};
|
|
7713
7787
|
var registered = false;
|