@helpai/elements 0.30.0 → 0.31.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/configurator.mjs +24 -1
- package/elements-web-component.esm.js +29 -29
- package/elements-web-component.esm.js.map +4 -4
- package/elements.cjs.js +29 -29
- package/elements.cjs.js.map +4 -4
- package/elements.esm.js +29 -29
- package/elements.esm.js.map +4 -4
- package/elements.js +27 -27
- package/elements.js.map +4 -4
- package/index.d.ts +87 -26
- package/index.mjs +628 -524
- package/package.json +1 -1
- package/schema.d.ts +105 -27
- package/schema.json +100 -2
- package/schema.mjs +27 -1
- package/web-component.d.ts +11 -5
- package/web-component.mjs +422 -346
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 = {
|
|
@@ -279,8 +275,8 @@ function resolveStrings(locale, override) {
|
|
|
279
275
|
function primaryTag(locale) {
|
|
280
276
|
return (locale.split("-")[0] ?? "en").toLowerCase();
|
|
281
277
|
}
|
|
282
|
-
function
|
|
283
|
-
return strings[
|
|
278
|
+
function localizeText(strings, value) {
|
|
279
|
+
return strings[value] ?? value;
|
|
284
280
|
}
|
|
285
281
|
function isNestedShape(override) {
|
|
286
282
|
if (!override) return false;
|
|
@@ -290,6 +286,51 @@ function isNestedShape(override) {
|
|
|
290
286
|
return false;
|
|
291
287
|
}
|
|
292
288
|
|
|
289
|
+
// src/i18n/overlay.ts
|
|
290
|
+
function pickTranslation(translations, locale) {
|
|
291
|
+
if (!translations?.length || !locale) return void 0;
|
|
292
|
+
return translations.find((t) => t.locale === locale);
|
|
293
|
+
}
|
|
294
|
+
function overlayContent(item, locale) {
|
|
295
|
+
const { translations, defaultLocale: _defaultLocale, ...base } = item;
|
|
296
|
+
const tr = pickTranslation(translations, locale);
|
|
297
|
+
if (!tr) return base;
|
|
298
|
+
return {
|
|
299
|
+
...base,
|
|
300
|
+
title: tr.title ?? base.title,
|
|
301
|
+
description: tr.description ?? base.description,
|
|
302
|
+
content: tr.content ?? base.content
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function overlayFormDef(def, locale) {
|
|
306
|
+
const { translations, defaultLocale: _defaultLocale, ...base } = def;
|
|
307
|
+
const tr = pickTranslation(translations, locale);
|
|
308
|
+
if (!tr) return base;
|
|
309
|
+
const fieldTrByName = new Map((tr.fields ?? []).map((f) => [f.name, f]));
|
|
310
|
+
const fields = base.fields.map((field) => {
|
|
311
|
+
const ftr = fieldTrByName.get(field.name);
|
|
312
|
+
if (!ftr) return field;
|
|
313
|
+
const optionTrByValue = new Map((ftr.options ?? []).map((o) => [o.value, o]));
|
|
314
|
+
return {
|
|
315
|
+
...field,
|
|
316
|
+
label: ftr.label ?? field.label,
|
|
317
|
+
placeholder: ftr.placeholder ?? field.placeholder,
|
|
318
|
+
validationHint: ftr.validationHint ?? field.validationHint,
|
|
319
|
+
options: field.options?.map((option) => {
|
|
320
|
+
const otr = optionTrByValue.get(option.value);
|
|
321
|
+
return otr ? { ...option, label: otr.label ?? option.label, description: otr.description ?? option.description } : option;
|
|
322
|
+
})
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
return {
|
|
326
|
+
...base,
|
|
327
|
+
title: tr.title ?? base.title,
|
|
328
|
+
description: tr.description ?? base.description,
|
|
329
|
+
submitLabel: tr.submitLabel ?? base.submitLabel,
|
|
330
|
+
fields
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
293
334
|
// src/core/storage.ts
|
|
294
335
|
var LocalStorageAdapter = class {
|
|
295
336
|
get(key) {
|
|
@@ -541,7 +582,7 @@ function resolveOptions(rawOpts) {
|
|
|
541
582
|
humanInLoop: opts.features?.humanInLoop ?? DEFAULT_FEATURES.humanInLoop,
|
|
542
583
|
tools: opts.features?.tools
|
|
543
584
|
},
|
|
544
|
-
forms: resolveForms(opts.forms),
|
|
585
|
+
forms: resolveForms(opts.forms, locale),
|
|
545
586
|
endpoints: {
|
|
546
587
|
upload: opts.endpoints?.upload ?? DEFAULT_ENDPOINTS.upload,
|
|
547
588
|
transcribe: opts.endpoints?.transcribe ?? DEFAULT_ENDPOINTS.transcribe,
|
|
@@ -664,13 +705,14 @@ function resolveModules(overrides) {
|
|
|
664
705
|
const byId = Object.fromEntries(list.map((m) => [m.id, m]));
|
|
665
706
|
return { list, byId };
|
|
666
707
|
}
|
|
667
|
-
function resolveForms(overrides) {
|
|
708
|
+
function resolveForms(overrides, locale) {
|
|
668
709
|
var _a;
|
|
669
710
|
if (!overrides?.length) return DEFAULT_FORMS;
|
|
670
711
|
const list = [];
|
|
671
712
|
const seen = /* @__PURE__ */ new Set();
|
|
672
|
-
for (const
|
|
673
|
-
if (!
|
|
713
|
+
for (const raw of overrides) {
|
|
714
|
+
if (!raw?.id || seen.has(raw.id)) continue;
|
|
715
|
+
const def = overlayFormDef(raw, locale);
|
|
674
716
|
const fields = (def.fields ?? []).filter((f) => Boolean(f && f.name && f.label && f.type));
|
|
675
717
|
const triggers = (Array.isArray(def.on) ? def.on : [def.on]).map(parseTrigger).filter((t) => t !== null);
|
|
676
718
|
if (fields.length === 0 || triggers.length === 0) continue;
|
|
@@ -993,35 +1035,153 @@ function parseLauncher(get, str2) {
|
|
|
993
1035
|
return Object.keys(base).length > 0 ? base : null;
|
|
994
1036
|
}
|
|
995
1037
|
|
|
996
|
-
// src/core/
|
|
997
|
-
function
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1038
|
+
// src/core/ids.ts
|
|
1039
|
+
function uuid7() {
|
|
1040
|
+
const ts = Date.now();
|
|
1041
|
+
const bytes = new Uint8Array(16);
|
|
1042
|
+
bytes[0] = Math.floor(ts / 2 ** 40) & 255;
|
|
1043
|
+
bytes[1] = Math.floor(ts / 2 ** 32) & 255;
|
|
1044
|
+
bytes[2] = ts >>> 24 & 255;
|
|
1045
|
+
bytes[3] = ts >>> 16 & 255;
|
|
1046
|
+
bytes[4] = ts >>> 8 & 255;
|
|
1047
|
+
bytes[5] = ts & 255;
|
|
1048
|
+
fillRandom(bytes.subarray(6));
|
|
1049
|
+
bytes[6] = bytes[6] & 15 | 112;
|
|
1050
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1051
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1052
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1002
1053
|
}
|
|
1003
|
-
function
|
|
1004
|
-
if (
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1054
|
+
function fillRandom(view) {
|
|
1055
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
1056
|
+
crypto.getRandomValues(view);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
for (let i = 0; i < view.length; i++) view[i] = Math.floor(Math.random() * 256);
|
|
1008
1060
|
}
|
|
1009
1061
|
|
|
1010
|
-
// src/core/
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
const
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1062
|
+
// src/core/persistence.ts
|
|
1063
|
+
var log2 = logger.scope("persistence");
|
|
1064
|
+
function createPersistence(widgetId, storage = defaultStorage) {
|
|
1065
|
+
const prefix = `${BRAND.cssPrefix}.${widgetId}`;
|
|
1066
|
+
const KEY_VISITOR = `${prefix}.visitorId`;
|
|
1067
|
+
const KEY_CONVERSATION = `${prefix}.conversationId`;
|
|
1068
|
+
const KEY_USER_PREFS = `${prefix}.userPrefs`;
|
|
1069
|
+
const KEY_PANEL_OPEN = `${prefix}.panelOpen`;
|
|
1070
|
+
const KEY_PANEL_SIZE = `${prefix}.panelSize`;
|
|
1071
|
+
const KEY_CALLOUT_DISMISSED = `${prefix}.calloutDismissed`;
|
|
1072
|
+
const KEY_SIDEBAR_COLLAPSED = `${prefix}.sidebarCollapsed`;
|
|
1073
|
+
const KEY_ACTIVE_MODULE = `${prefix}.activeModule`;
|
|
1074
|
+
const KEY_FORMS_DONE = `${prefix}.formsDone`;
|
|
1075
|
+
const persistence = {
|
|
1076
|
+
getVisitorId() {
|
|
1077
|
+
const existing = storage.get(KEY_VISITOR);
|
|
1078
|
+
if (existing) return existing;
|
|
1079
|
+
const minted = uuid7();
|
|
1080
|
+
storage.set(KEY_VISITOR, minted);
|
|
1081
|
+
return minted;
|
|
1082
|
+
},
|
|
1083
|
+
setVisitorId(id) {
|
|
1084
|
+
log2.debug("setVisitorId (rebind)", { id, widgetId });
|
|
1085
|
+
storage.set(KEY_VISITOR, id);
|
|
1086
|
+
},
|
|
1087
|
+
loadConversationId() {
|
|
1088
|
+
return storage.get(KEY_CONVERSATION) ?? void 0;
|
|
1089
|
+
},
|
|
1090
|
+
saveConversationId(id) {
|
|
1091
|
+
log2.trace("saveConversationId", { id, widgetId });
|
|
1092
|
+
if (id) storage.set(KEY_CONVERSATION, id);
|
|
1093
|
+
else storage.remove(KEY_CONVERSATION);
|
|
1094
|
+
},
|
|
1095
|
+
loadUserPrefs() {
|
|
1096
|
+
const raw = storage.get(KEY_USER_PREFS);
|
|
1097
|
+
if (!raw) return {};
|
|
1098
|
+
try {
|
|
1099
|
+
const parsed = JSON.parse(raw);
|
|
1100
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
log2.warn("loadUserPrefs parse failed", { error });
|
|
1103
|
+
return {};
|
|
1104
|
+
}
|
|
1105
|
+
},
|
|
1106
|
+
saveUserPrefs(s) {
|
|
1107
|
+
try {
|
|
1108
|
+
storage.set(KEY_USER_PREFS, JSON.stringify(s));
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
log2.warn("saveUserPrefs serialise failed", { error });
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
patchUserPrefs(patch) {
|
|
1114
|
+
const current = persistence.loadUserPrefs();
|
|
1115
|
+
persistence.saveUserPrefs({ ...current, ...patch });
|
|
1116
|
+
},
|
|
1117
|
+
loadPanelOpen() {
|
|
1118
|
+
const raw = storage.get(KEY_PANEL_OPEN);
|
|
1119
|
+
if (raw === "1") return true;
|
|
1120
|
+
if (raw === "0") return false;
|
|
1121
|
+
return null;
|
|
1122
|
+
},
|
|
1123
|
+
savePanelOpen(open) {
|
|
1124
|
+
storage.set(KEY_PANEL_OPEN, open ? "1" : "0");
|
|
1125
|
+
},
|
|
1126
|
+
loadPanelSize() {
|
|
1127
|
+
const raw = storage.get(KEY_PANEL_SIZE);
|
|
1128
|
+
return raw === "normal" || raw === "expanded" || raw === "fullscreen" ? raw : null;
|
|
1129
|
+
},
|
|
1130
|
+
savePanelSize(size) {
|
|
1131
|
+
storage.set(KEY_PANEL_SIZE, size);
|
|
1132
|
+
},
|
|
1133
|
+
loadCalloutDismissed(currentText) {
|
|
1134
|
+
if (!currentText) return false;
|
|
1135
|
+
const dismissedText = storage.get(KEY_CALLOUT_DISMISSED);
|
|
1136
|
+
return !!dismissedText && dismissedText === currentText;
|
|
1137
|
+
},
|
|
1138
|
+
saveCalloutDismissed(currentText) {
|
|
1139
|
+
if (currentText) storage.set(KEY_CALLOUT_DISMISSED, currentText);
|
|
1140
|
+
else storage.remove(KEY_CALLOUT_DISMISSED);
|
|
1141
|
+
},
|
|
1142
|
+
loadSidebarCollapsed() {
|
|
1143
|
+
const raw = storage.get(KEY_SIDEBAR_COLLAPSED);
|
|
1144
|
+
if (raw === "1") return true;
|
|
1145
|
+
if (raw === "0") return false;
|
|
1146
|
+
return null;
|
|
1147
|
+
},
|
|
1148
|
+
saveSidebarCollapsed(collapsed) {
|
|
1149
|
+
storage.set(KEY_SIDEBAR_COLLAPSED, collapsed ? "1" : "0");
|
|
1150
|
+
},
|
|
1151
|
+
loadActiveModule() {
|
|
1152
|
+
return storage.get(KEY_ACTIVE_MODULE) || null;
|
|
1153
|
+
},
|
|
1154
|
+
saveActiveModule(id) {
|
|
1155
|
+
storage.set(KEY_ACTIVE_MODULE, id);
|
|
1156
|
+
},
|
|
1157
|
+
loadFormsDone() {
|
|
1158
|
+
const raw = storage.get(KEY_FORMS_DONE);
|
|
1159
|
+
if (!raw) return {};
|
|
1160
|
+
try {
|
|
1161
|
+
const parsed = JSON.parse(raw);
|
|
1162
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
log2.warn("loadFormsDone parse failed", { error });
|
|
1165
|
+
return {};
|
|
1166
|
+
}
|
|
1167
|
+
},
|
|
1168
|
+
saveFormDone(id) {
|
|
1169
|
+
try {
|
|
1170
|
+
const done = persistence.loadFormsDone();
|
|
1171
|
+
done[id] = Date.now();
|
|
1172
|
+
storage.set(KEY_FORMS_DONE, JSON.stringify(done));
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
log2.warn("saveFormDone serialise failed", { error });
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
clearConversation() {
|
|
1178
|
+
storage.remove(KEY_CONVERSATION);
|
|
1179
|
+
}
|
|
1024
1180
|
};
|
|
1181
|
+
return persistence;
|
|
1182
|
+
}
|
|
1183
|
+
function isPlainObject(v) {
|
|
1184
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1025
1185
|
}
|
|
1026
1186
|
|
|
1027
1187
|
// src/styles/tokens.css
|
|
@@ -1048,7 +1208,7 @@ var FONT = true ? "Inter, system-ui, -apple-system, 'Segoe UI', sans-serif" : "I
|
|
|
1048
1208
|
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);
|
|
1049
1209
|
|
|
1050
1210
|
// src/shadow/mount.ts
|
|
1051
|
-
var
|
|
1211
|
+
var log3 = logger.scope("mount");
|
|
1052
1212
|
var sheetsByDocument = /* @__PURE__ */ new WeakMap();
|
|
1053
1213
|
var HOST_CSS = {
|
|
1054
1214
|
floating: "all: initial; position: fixed; z-index: 2147483600;",
|
|
@@ -1065,7 +1225,7 @@ function hostPositionForMode(mode) {
|
|
|
1065
1225
|
}
|
|
1066
1226
|
function createShadowMount(opts = {}) {
|
|
1067
1227
|
const position = opts.position ?? "flow";
|
|
1068
|
-
|
|
1228
|
+
log3.debug("createShadowMount", { position, hasTarget: !!opts.target, hasHost: !!opts.existingHost });
|
|
1069
1229
|
const tagName = `${BRAND.cssPrefix}-chat-host`;
|
|
1070
1230
|
if (typeof customElements !== "undefined" && !customElements.get(tagName)) {
|
|
1071
1231
|
customElements.define(tagName, class extends HTMLElement {
|
|
@@ -1149,6 +1309,44 @@ function applyHostAttributes(host, resolved) {
|
|
|
1149
1309
|
applySize(host, resolved.size);
|
|
1150
1310
|
}
|
|
1151
1311
|
|
|
1312
|
+
// src/core/boot.ts
|
|
1313
|
+
import { h, render as renderPreact } from "preact";
|
|
1314
|
+
|
|
1315
|
+
// src/ui/app.tsx
|
|
1316
|
+
import { useCallback as useCallback6, useEffect as useEffect17, useMemo as useMemo3, useRef as useRef9, useState as useState13 } from "preact/hooks";
|
|
1317
|
+
import { useComputed as useComputed7, useSignal as useSignal2 } from "@preact/signals";
|
|
1318
|
+
|
|
1319
|
+
// src/core/handshake-shape.ts
|
|
1320
|
+
function isPlainObject2(raw) {
|
|
1321
|
+
return !!raw && typeof raw === "object" && !Array.isArray(raw);
|
|
1322
|
+
}
|
|
1323
|
+
function isSiteConfigShape(raw) {
|
|
1324
|
+
return isPlainObject2(raw);
|
|
1325
|
+
}
|
|
1326
|
+
function isBlocksConfigShape(raw) {
|
|
1327
|
+
if (!isPlainObject2(raw)) return false;
|
|
1328
|
+
if (raw.navigation !== void 0 && !Array.isArray(raw.navigation)) return false;
|
|
1329
|
+
if (raw.linkCards !== void 0 && !Array.isArray(raw.linkCards)) return false;
|
|
1330
|
+
return true;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// src/core/host-commands.ts
|
|
1334
|
+
function commandEventName(cmd) {
|
|
1335
|
+
return `${BRAND.cssPrefix}:${cmd}`;
|
|
1336
|
+
}
|
|
1337
|
+
function bindHostCommands(host, handlers) {
|
|
1338
|
+
const entries = Object.entries(handlers);
|
|
1339
|
+
const wrapped = entries.map(([cmd, fn]) => {
|
|
1340
|
+
const event = commandEventName(cmd);
|
|
1341
|
+
const listener = (e) => fn(e.detail);
|
|
1342
|
+
host.addEventListener(event, listener);
|
|
1343
|
+
return [event, listener];
|
|
1344
|
+
});
|
|
1345
|
+
return () => {
|
|
1346
|
+
for (const [event, listener] of wrapped) host.removeEventListener(event, listener);
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1152
1350
|
// src/ui/host-mode.ts
|
|
1153
1351
|
function computeHostMode(configured, panelSize, isOpen) {
|
|
1154
1352
|
if (configured === "page") return "page";
|
|
@@ -1199,30 +1397,6 @@ function createAuth(opts) {
|
|
|
1199
1397
|
};
|
|
1200
1398
|
}
|
|
1201
1399
|
|
|
1202
|
-
// src/core/ids.ts
|
|
1203
|
-
function uuid7() {
|
|
1204
|
-
const ts = Date.now();
|
|
1205
|
-
const bytes = new Uint8Array(16);
|
|
1206
|
-
bytes[0] = Math.floor(ts / 2 ** 40) & 255;
|
|
1207
|
-
bytes[1] = Math.floor(ts / 2 ** 32) & 255;
|
|
1208
|
-
bytes[2] = ts >>> 24 & 255;
|
|
1209
|
-
bytes[3] = ts >>> 16 & 255;
|
|
1210
|
-
bytes[4] = ts >>> 8 & 255;
|
|
1211
|
-
bytes[5] = ts & 255;
|
|
1212
|
-
fillRandom(bytes.subarray(6));
|
|
1213
|
-
bytes[6] = bytes[6] & 15 | 112;
|
|
1214
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
1215
|
-
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1216
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1217
|
-
}
|
|
1218
|
-
function fillRandom(view) {
|
|
1219
|
-
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
1220
|
-
crypto.getRandomValues(view);
|
|
1221
|
-
return;
|
|
1222
|
-
}
|
|
1223
|
-
for (let i = 0; i < view.length; i++) view[i] = Math.floor(Math.random() * 256);
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
1400
|
// src/stream/types.ts
|
|
1227
1401
|
var StreamError = class extends Error {
|
|
1228
1402
|
constructor(message, code, status, cause) {
|
|
@@ -1235,7 +1409,7 @@ var StreamError = class extends Error {
|
|
|
1235
1409
|
};
|
|
1236
1410
|
|
|
1237
1411
|
// src/stream/parser.ts
|
|
1238
|
-
var
|
|
1412
|
+
var log4 = logger.scope("parser");
|
|
1239
1413
|
async function* parseChatStream(response, signal7) {
|
|
1240
1414
|
if (!response.ok) {
|
|
1241
1415
|
const text = await response.text().catch(() => "");
|
|
@@ -1287,10 +1461,10 @@ ${part}` : part;
|
|
|
1287
1461
|
if (!dataPayload || dataPayload === "[DONE]") return null;
|
|
1288
1462
|
try {
|
|
1289
1463
|
const chunk = JSON.parse(dataPayload);
|
|
1290
|
-
|
|
1464
|
+
log4.trace("chunk", { type: chunk.type, eventId });
|
|
1291
1465
|
return { chunk, eventId };
|
|
1292
1466
|
} catch (err) {
|
|
1293
|
-
|
|
1467
|
+
log4.trace("frame parse failed", { err, payload: dataPayload.slice(0, 200) });
|
|
1294
1468
|
return null;
|
|
1295
1469
|
}
|
|
1296
1470
|
}
|
|
@@ -1445,7 +1619,7 @@ function messageToWireParts(m) {
|
|
|
1445
1619
|
}
|
|
1446
1620
|
|
|
1447
1621
|
// src/stream/transport.ts
|
|
1448
|
-
var
|
|
1622
|
+
var log5 = logger.scope("transport");
|
|
1449
1623
|
var MAX_RESUME_ATTEMPTS = 3;
|
|
1450
1624
|
var RESUME_BACKOFF_MS = 400;
|
|
1451
1625
|
var CONTENT_CACHE_TTL_MS = 6e4;
|
|
@@ -1571,13 +1745,13 @@ var AgentTransport = class {
|
|
|
1571
1745
|
}
|
|
1572
1746
|
/** One-shot runtime bootstrap. Called once after the widget mounts. */
|
|
1573
1747
|
async handshake(body) {
|
|
1574
|
-
|
|
1748
|
+
log5.debug("handshake \u2192", {
|
|
1575
1749
|
visitorId: body.visitorId,
|
|
1576
1750
|
conversationId: body.conversationId,
|
|
1577
1751
|
locale: body.locale
|
|
1578
1752
|
});
|
|
1579
1753
|
const res = await this.postJson(DEFAULT_PATHS.handshake, body, "handshake");
|
|
1580
|
-
|
|
1754
|
+
log5.debug("handshake \u2190", {
|
|
1581
1755
|
visitorId: res.visitorId,
|
|
1582
1756
|
conversationId: res.conversationId,
|
|
1583
1757
|
hasWelcome: !!res.welcome,
|
|
@@ -1585,7 +1759,7 @@ var AgentTransport = class {
|
|
|
1585
1759
|
rebind: res.rebind
|
|
1586
1760
|
});
|
|
1587
1761
|
if (!isHandshakeResponseShape(res)) {
|
|
1588
|
-
|
|
1762
|
+
log5.warn("handshake response did not match expected shape; using raw response", { response: res });
|
|
1589
1763
|
}
|
|
1590
1764
|
this.visitorId = res.visitorId ?? body.visitorId;
|
|
1591
1765
|
this.conversationId = res.conversationId;
|
|
@@ -1593,11 +1767,11 @@ var AgentTransport = class {
|
|
|
1593
1767
|
}
|
|
1594
1768
|
/** Upload a file attachment. Returns the `{id, url}` the backend assigns. */
|
|
1595
1769
|
async uploadFile(file) {
|
|
1596
|
-
|
|
1770
|
+
log5.debug("uploadFile \u2192", { name: file.name, size: file.size, type: file.type });
|
|
1597
1771
|
const fd = new FormData();
|
|
1598
1772
|
fd.append("file", file, file.name);
|
|
1599
1773
|
const res = await this.postForm(this.uploadPath, fd, "upload");
|
|
1600
|
-
|
|
1774
|
+
log5.debug("uploadFile \u2190", { id: res.id, url: res.url });
|
|
1601
1775
|
return res;
|
|
1602
1776
|
}
|
|
1603
1777
|
/**
|
|
@@ -1605,12 +1779,12 @@ var AgentTransport = class {
|
|
|
1605
1779
|
* Only used when `features.voice === 'server'`.
|
|
1606
1780
|
*/
|
|
1607
1781
|
async transcribe(audio, mimeType = "audio/webm") {
|
|
1608
|
-
|
|
1782
|
+
log5.debug("transcribe \u2192", { size: audio.size, mimeType });
|
|
1609
1783
|
const fd = new FormData();
|
|
1610
1784
|
fd.append("audio", audio, `voice.${extensionFor(mimeType)}`);
|
|
1611
1785
|
fd.append("mimeType", mimeType);
|
|
1612
1786
|
const res = await this.postForm(this.transcribePath, fd, "transcribe");
|
|
1613
|
-
|
|
1787
|
+
log5.debug("transcribe \u2190", { textLen: res.text.length });
|
|
1614
1788
|
return res;
|
|
1615
1789
|
}
|
|
1616
1790
|
async listConversations(params = {}) {
|
|
@@ -1618,14 +1792,14 @@ var AgentTransport = class {
|
|
|
1618
1792
|
if (params.visitorId) url.searchParams.set("visitorId", params.visitorId);
|
|
1619
1793
|
if (params.limit) url.searchParams.set("limit", String(params.limit));
|
|
1620
1794
|
if (params.cursor) url.searchParams.set("cursor", params.cursor);
|
|
1621
|
-
|
|
1795
|
+
log5.debug("listConversations \u2192", {
|
|
1622
1796
|
visitorId: params.visitorId ?? this.visitorId,
|
|
1623
1797
|
limit: params.limit,
|
|
1624
1798
|
cursor: params.cursor
|
|
1625
1799
|
});
|
|
1626
1800
|
const res = await this.getJson(url.toString(), "listConversations");
|
|
1627
1801
|
const conversations = res.conversations ?? [];
|
|
1628
|
-
|
|
1802
|
+
log5.debug("listConversations \u2190", { count: conversations.length, nextCursor: res.nextCursor });
|
|
1629
1803
|
return {
|
|
1630
1804
|
conversations: conversations.map((conversation) => ({
|
|
1631
1805
|
conversationId: conversation.conversationId,
|
|
@@ -1641,10 +1815,10 @@ var AgentTransport = class {
|
|
|
1641
1815
|
async loadConversation(conversationId) {
|
|
1642
1816
|
const url = new URL(this.url(DEFAULT_PATHS.listMessages));
|
|
1643
1817
|
url.searchParams.set("conversationId", conversationId);
|
|
1644
|
-
|
|
1818
|
+
log5.debug("loadConversation \u2192", { conversationId, visitorId: this.visitorId });
|
|
1645
1819
|
const res = await this.getJson(url.toString(), "loadConversation");
|
|
1646
1820
|
const messages = res.messages ?? [];
|
|
1647
|
-
|
|
1821
|
+
log5.debug("loadConversation \u2190", {
|
|
1648
1822
|
conversationId: res.conversationId,
|
|
1649
1823
|
canContinue: res.canContinue,
|
|
1650
1824
|
messageCount: messages.length,
|
|
@@ -1666,19 +1840,19 @@ var AgentTransport = class {
|
|
|
1666
1840
|
*/
|
|
1667
1841
|
async markRead(conversationId) {
|
|
1668
1842
|
if (!this.visitorId || !conversationId) return;
|
|
1669
|
-
|
|
1843
|
+
log5.debug("markRead \u2192", { conversationId, visitorId: this.visitorId });
|
|
1670
1844
|
try {
|
|
1671
1845
|
await this.postJson(DEFAULT_PATHS.markRead, { conversationId }, "markRead");
|
|
1672
1846
|
} catch (err) {
|
|
1673
|
-
|
|
1847
|
+
log5.debug("markRead failed (non-fatal)", { err });
|
|
1674
1848
|
}
|
|
1675
1849
|
}
|
|
1676
1850
|
/** Fetch content rows by filter. `GET /pai/content` (data-API), TTL-cached. */
|
|
1677
1851
|
listContent(query = {}) {
|
|
1678
|
-
const key = JSON.stringify(query, Object.keys(query).toSorted())
|
|
1852
|
+
const key = `${this.locale ?? ""}|${JSON.stringify(query, Object.keys(query).toSorted())}`;
|
|
1679
1853
|
const cached = this.contentCache.get(key);
|
|
1680
1854
|
if (cached && Date.now() - cached.at < CONTENT_CACHE_TTL_MS) {
|
|
1681
|
-
|
|
1855
|
+
log5.debug("listContent \u2192 cache", query);
|
|
1682
1856
|
return cached.result;
|
|
1683
1857
|
}
|
|
1684
1858
|
const result = this.fetchContent(query).catch((err) => {
|
|
@@ -1694,10 +1868,10 @@ var AgentTransport = class {
|
|
|
1694
1868
|
if (value === void 0) continue;
|
|
1695
1869
|
url.searchParams.set(key, Array.isArray(value) ? value.join(",") : String(value));
|
|
1696
1870
|
}
|
|
1697
|
-
|
|
1871
|
+
log5.debug("listContent \u2192", query);
|
|
1698
1872
|
const res = await this.getJson(url.toString(), "listContent");
|
|
1699
|
-
const items = res.items ?? [];
|
|
1700
|
-
|
|
1873
|
+
const items = (res.items ?? []).map((item) => overlayContent(item, this.locale));
|
|
1874
|
+
log5.debug("listContent \u2190", { count: items.length, total: res.pagination?.total });
|
|
1701
1875
|
return { ...res, items };
|
|
1702
1876
|
}
|
|
1703
1877
|
/**
|
|
@@ -1715,21 +1889,21 @@ var AgentTransport = class {
|
|
|
1715
1889
|
async bootstrap(conversationId) {
|
|
1716
1890
|
const url = new URL(this.dataUrl(DEFAULT_PATHS.bootstrap));
|
|
1717
1891
|
if (conversationId) url.searchParams.set("conversationId", conversationId);
|
|
1718
|
-
|
|
1892
|
+
log5.debug("bootstrap \u2192", { conversationId: conversationId ?? this.conversationId });
|
|
1719
1893
|
const res = await this.getJson(url.toString(), "bootstrap");
|
|
1720
1894
|
const forms = res.forms ?? [];
|
|
1721
1895
|
const formSubmissions = res.formSubmissions ?? [];
|
|
1722
|
-
|
|
1896
|
+
log5.debug("bootstrap \u2190", { forms: forms.length, formSubmissions: formSubmissions.length });
|
|
1723
1897
|
return { forms, formSubmissions };
|
|
1724
1898
|
}
|
|
1725
1899
|
async saveUserPrefs(userPrefs) {
|
|
1726
|
-
|
|
1900
|
+
log5.debug("saveUserPrefs \u2192", {
|
|
1727
1901
|
visitorId: this.visitorId,
|
|
1728
1902
|
conversationId: this.conversationId,
|
|
1729
1903
|
settingsKeys: Object.keys(userPrefs)
|
|
1730
1904
|
});
|
|
1731
1905
|
const res = await this.postJson(DEFAULT_PATHS.updateSettings, { userPrefs }, "saveUserPrefs");
|
|
1732
|
-
|
|
1906
|
+
log5.debug("saveUserPrefs \u2190", { ok: res.ok });
|
|
1733
1907
|
return res;
|
|
1734
1908
|
}
|
|
1735
1909
|
get uploadPath() {
|
|
@@ -1750,15 +1924,15 @@ var AgentTransport = class {
|
|
|
1750
1924
|
* that doesn't implement the endpoint) is non-fatal, so callers don't await.
|
|
1751
1925
|
*/
|
|
1752
1926
|
async submitForm(body) {
|
|
1753
|
-
|
|
1927
|
+
log5.debug("submitForm \u2192", { formId: body.formId, trigger: body.trigger, fields: Object.keys(body.values).length });
|
|
1754
1928
|
try {
|
|
1755
1929
|
await this.postJson(this.dataUrl(this.submitFormPath), body, "submitForm");
|
|
1756
1930
|
} catch (err) {
|
|
1757
|
-
|
|
1931
|
+
log5.debug("submitForm failed (non-fatal)", { err });
|
|
1758
1932
|
}
|
|
1759
1933
|
}
|
|
1760
1934
|
sendMessage(body) {
|
|
1761
|
-
|
|
1935
|
+
log5.debug("message \u2192", {
|
|
1762
1936
|
conversationId: body.conversationId,
|
|
1763
1937
|
visitorId: body.visitorId,
|
|
1764
1938
|
messageCount: body.messages.length,
|
|
@@ -1838,17 +2012,17 @@ var AgentTransport = class {
|
|
|
1838
2012
|
});
|
|
1839
2013
|
} catch (err) {
|
|
1840
2014
|
if (ctrl.signal.aborted) return;
|
|
1841
|
-
|
|
2015
|
+
log5.debug("resume fetch failed \u2014 retrying", { attempt, err });
|
|
1842
2016
|
continue;
|
|
1843
2017
|
}
|
|
1844
2018
|
if (res.status === 204 || !res.ok) {
|
|
1845
|
-
|
|
2019
|
+
log5.debug("resume \u2192 no in-flight stream", { status: res.status });
|
|
1846
2020
|
return;
|
|
1847
2021
|
}
|
|
1848
2022
|
const before = seenIds.size;
|
|
1849
2023
|
if (yield* this.drain(parseChatStream(res, ctrl.signal), seenIds, ctrl)) return;
|
|
1850
2024
|
if (seenIds.size === before) {
|
|
1851
|
-
|
|
2025
|
+
log5.debug("resume \u2192 stream closed with no new events; nothing to resume");
|
|
1852
2026
|
return;
|
|
1853
2027
|
}
|
|
1854
2028
|
}
|
|
@@ -1875,14 +2049,14 @@ var AgentTransport = class {
|
|
|
1875
2049
|
} catch (err) {
|
|
1876
2050
|
if (ctrl.signal.aborted) return true;
|
|
1877
2051
|
if (err instanceof StreamError && err.status !== void 0) throw err;
|
|
1878
|
-
|
|
2052
|
+
log5.debug("stream segment dropped", { err });
|
|
1879
2053
|
}
|
|
1880
2054
|
return false;
|
|
1881
2055
|
}
|
|
1882
2056
|
/** Abort + fire-and-forget POST to `/pai/cancel-stream`. Idempotent. */
|
|
1883
2057
|
cancelStream(ctrl, conversationId) {
|
|
1884
2058
|
if (ctrl.signal.aborted) return;
|
|
1885
|
-
|
|
2059
|
+
log5.debug("cancel stream", { conversationId, visitorId: this.visitorId });
|
|
1886
2060
|
ctrl.abort();
|
|
1887
2061
|
if (!this.visitorId || !conversationId) return;
|
|
1888
2062
|
this.fetchImpl(this.url(DEFAULT_PATHS.cancelStream), {
|
|
@@ -1891,7 +2065,7 @@ var AgentTransport = class {
|
|
|
1891
2065
|
headers: { "content-type": "application/json", ...this.opts.auth.headers() },
|
|
1892
2066
|
body: JSON.stringify(this.withEnvelope({})),
|
|
1893
2067
|
keepalive: true
|
|
1894
|
-
}).catch((err) =>
|
|
2068
|
+
}).catch((err) => log5.debug("cancel POST failed (non-fatal)", { err }));
|
|
1895
2069
|
}
|
|
1896
2070
|
// ---- Low-level fetch helpers ------------------------------------------
|
|
1897
2071
|
async *openMessageStream(body, signal7) {
|
|
@@ -1973,13 +2147,13 @@ var AgentTransport = class {
|
|
|
1973
2147
|
if (attempt >= MAX_REQUEST_RETRIES) {
|
|
1974
2148
|
throw new StreamError(`${label} failed: network error`, "network", void 0, err);
|
|
1975
2149
|
}
|
|
1976
|
-
|
|
2150
|
+
log5.debug("request network error \u2014 retrying", { label, attempt });
|
|
1977
2151
|
await sleep(backoffMs(attempt));
|
|
1978
2152
|
continue;
|
|
1979
2153
|
}
|
|
1980
2154
|
if (res.ok || !RETRYABLE_STATUS.has(res.status) || attempt >= MAX_REQUEST_RETRIES) return res;
|
|
1981
2155
|
const wait = retryAfterMs(res.headers) ?? backoffMs(attempt);
|
|
1982
|
-
|
|
2156
|
+
log5.debug("request retryable status \u2014 retrying", { label, status: res.status, attempt, wait });
|
|
1983
2157
|
await sleep(wait);
|
|
1984
2158
|
}
|
|
1985
2159
|
}
|
|
@@ -2174,7 +2348,7 @@ var TRIGGER = {
|
|
|
2174
2348
|
};
|
|
2175
2349
|
|
|
2176
2350
|
// src/stream/reducer.ts
|
|
2177
|
-
var
|
|
2351
|
+
var log6 = logger.scope("reducer");
|
|
2178
2352
|
var StreamReducer = class {
|
|
2179
2353
|
constructor(messagesSig) {
|
|
2180
2354
|
__publicField(this, "messagesSig", messagesSig);
|
|
@@ -2196,13 +2370,13 @@ var StreamReducer = class {
|
|
|
2196
2370
|
case "finish-step":
|
|
2197
2371
|
return;
|
|
2198
2372
|
case "finish":
|
|
2199
|
-
|
|
2373
|
+
log6.debug("finish", { reason: chunk.finishReason, canContinue: chunk.canContinue });
|
|
2200
2374
|
m.status = "complete";
|
|
2201
2375
|
if (chunk.finishReason) m.finishReason = chunk.finishReason;
|
|
2202
2376
|
this.bumpDone(m);
|
|
2203
2377
|
return;
|
|
2204
2378
|
case "error":
|
|
2205
|
-
|
|
2379
|
+
log6.warn("stream error chunk", { errorText: chunk.errorText });
|
|
2206
2380
|
m.status = "error";
|
|
2207
2381
|
m.errorText = chunk.errorText;
|
|
2208
2382
|
this.bumpDone(m);
|
|
@@ -2331,131 +2505,6 @@ function applyTool(m, chunk) {
|
|
|
2331
2505
|
}
|
|
2332
2506
|
}
|
|
2333
2507
|
|
|
2334
|
-
// src/core/persistence.ts
|
|
2335
|
-
var log6 = logger.scope("persistence");
|
|
2336
|
-
function createPersistence(widgetId, storage = defaultStorage) {
|
|
2337
|
-
const prefix = `${BRAND.cssPrefix}.${widgetId}`;
|
|
2338
|
-
const KEY_VISITOR = `${prefix}.visitorId`;
|
|
2339
|
-
const KEY_CONVERSATION = `${prefix}.conversationId`;
|
|
2340
|
-
const KEY_USER_PREFS = `${prefix}.userPrefs`;
|
|
2341
|
-
const KEY_PANEL_OPEN = `${prefix}.panelOpen`;
|
|
2342
|
-
const KEY_PANEL_SIZE = `${prefix}.panelSize`;
|
|
2343
|
-
const KEY_CALLOUT_DISMISSED = `${prefix}.calloutDismissed`;
|
|
2344
|
-
const KEY_SIDEBAR_COLLAPSED = `${prefix}.sidebarCollapsed`;
|
|
2345
|
-
const KEY_ACTIVE_MODULE = `${prefix}.activeModule`;
|
|
2346
|
-
const KEY_FORMS_DONE = `${prefix}.formsDone`;
|
|
2347
|
-
const persistence = {
|
|
2348
|
-
getVisitorId() {
|
|
2349
|
-
const existing = storage.get(KEY_VISITOR);
|
|
2350
|
-
if (existing) return existing;
|
|
2351
|
-
const minted = uuid7();
|
|
2352
|
-
storage.set(KEY_VISITOR, minted);
|
|
2353
|
-
return minted;
|
|
2354
|
-
},
|
|
2355
|
-
setVisitorId(id) {
|
|
2356
|
-
log6.debug("setVisitorId (rebind)", { id, widgetId });
|
|
2357
|
-
storage.set(KEY_VISITOR, id);
|
|
2358
|
-
},
|
|
2359
|
-
loadConversationId() {
|
|
2360
|
-
return storage.get(KEY_CONVERSATION) ?? void 0;
|
|
2361
|
-
},
|
|
2362
|
-
saveConversationId(id) {
|
|
2363
|
-
log6.trace("saveConversationId", { id, widgetId });
|
|
2364
|
-
if (id) storage.set(KEY_CONVERSATION, id);
|
|
2365
|
-
else storage.remove(KEY_CONVERSATION);
|
|
2366
|
-
},
|
|
2367
|
-
loadUserPrefs() {
|
|
2368
|
-
const raw = storage.get(KEY_USER_PREFS);
|
|
2369
|
-
if (!raw) return {};
|
|
2370
|
-
try {
|
|
2371
|
-
const parsed = JSON.parse(raw);
|
|
2372
|
-
return isPlainObject2(parsed) ? parsed : {};
|
|
2373
|
-
} catch (error) {
|
|
2374
|
-
log6.warn("loadUserPrefs parse failed", { error });
|
|
2375
|
-
return {};
|
|
2376
|
-
}
|
|
2377
|
-
},
|
|
2378
|
-
saveUserPrefs(s) {
|
|
2379
|
-
try {
|
|
2380
|
-
storage.set(KEY_USER_PREFS, JSON.stringify(s));
|
|
2381
|
-
} catch (error) {
|
|
2382
|
-
log6.warn("saveUserPrefs serialise failed", { error });
|
|
2383
|
-
}
|
|
2384
|
-
},
|
|
2385
|
-
patchUserPrefs(patch) {
|
|
2386
|
-
const current = persistence.loadUserPrefs();
|
|
2387
|
-
persistence.saveUserPrefs({ ...current, ...patch });
|
|
2388
|
-
},
|
|
2389
|
-
loadPanelOpen() {
|
|
2390
|
-
const raw = storage.get(KEY_PANEL_OPEN);
|
|
2391
|
-
if (raw === "1") return true;
|
|
2392
|
-
if (raw === "0") return false;
|
|
2393
|
-
return null;
|
|
2394
|
-
},
|
|
2395
|
-
savePanelOpen(open) {
|
|
2396
|
-
storage.set(KEY_PANEL_OPEN, open ? "1" : "0");
|
|
2397
|
-
},
|
|
2398
|
-
loadPanelSize() {
|
|
2399
|
-
const raw = storage.get(KEY_PANEL_SIZE);
|
|
2400
|
-
return raw === "normal" || raw === "expanded" || raw === "fullscreen" ? raw : null;
|
|
2401
|
-
},
|
|
2402
|
-
savePanelSize(size) {
|
|
2403
|
-
storage.set(KEY_PANEL_SIZE, size);
|
|
2404
|
-
},
|
|
2405
|
-
loadCalloutDismissed(currentText) {
|
|
2406
|
-
if (!currentText) return false;
|
|
2407
|
-
const dismissedText = storage.get(KEY_CALLOUT_DISMISSED);
|
|
2408
|
-
return !!dismissedText && dismissedText === currentText;
|
|
2409
|
-
},
|
|
2410
|
-
saveCalloutDismissed(currentText) {
|
|
2411
|
-
if (currentText) storage.set(KEY_CALLOUT_DISMISSED, currentText);
|
|
2412
|
-
else storage.remove(KEY_CALLOUT_DISMISSED);
|
|
2413
|
-
},
|
|
2414
|
-
loadSidebarCollapsed() {
|
|
2415
|
-
const raw = storage.get(KEY_SIDEBAR_COLLAPSED);
|
|
2416
|
-
if (raw === "1") return true;
|
|
2417
|
-
if (raw === "0") return false;
|
|
2418
|
-
return null;
|
|
2419
|
-
},
|
|
2420
|
-
saveSidebarCollapsed(collapsed) {
|
|
2421
|
-
storage.set(KEY_SIDEBAR_COLLAPSED, collapsed ? "1" : "0");
|
|
2422
|
-
},
|
|
2423
|
-
loadActiveModule() {
|
|
2424
|
-
return storage.get(KEY_ACTIVE_MODULE) || null;
|
|
2425
|
-
},
|
|
2426
|
-
saveActiveModule(id) {
|
|
2427
|
-
storage.set(KEY_ACTIVE_MODULE, id);
|
|
2428
|
-
},
|
|
2429
|
-
loadFormsDone() {
|
|
2430
|
-
const raw = storage.get(KEY_FORMS_DONE);
|
|
2431
|
-
if (!raw) return {};
|
|
2432
|
-
try {
|
|
2433
|
-
const parsed = JSON.parse(raw);
|
|
2434
|
-
return isPlainObject2(parsed) ? parsed : {};
|
|
2435
|
-
} catch (error) {
|
|
2436
|
-
log6.warn("loadFormsDone parse failed", { error });
|
|
2437
|
-
return {};
|
|
2438
|
-
}
|
|
2439
|
-
},
|
|
2440
|
-
saveFormDone(id) {
|
|
2441
|
-
try {
|
|
2442
|
-
const done = persistence.loadFormsDone();
|
|
2443
|
-
done[id] = Date.now();
|
|
2444
|
-
storage.set(KEY_FORMS_DONE, JSON.stringify(done));
|
|
2445
|
-
} catch (error) {
|
|
2446
|
-
log6.warn("saveFormDone serialise failed", { error });
|
|
2447
|
-
}
|
|
2448
|
-
},
|
|
2449
|
-
clearConversation() {
|
|
2450
|
-
storage.remove(KEY_CONVERSATION);
|
|
2451
|
-
}
|
|
2452
|
-
};
|
|
2453
|
-
return persistence;
|
|
2454
|
-
}
|
|
2455
|
-
function isPlainObject2(v) {
|
|
2456
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2457
|
-
}
|
|
2458
|
-
|
|
2459
2508
|
// src/core/feedback/index.ts
|
|
2460
2509
|
import { signal as signal3 } from "@preact/signals";
|
|
2461
2510
|
|
|
@@ -3081,7 +3130,7 @@ function mountRawSvg(host, source) {
|
|
|
3081
3130
|
} catch {
|
|
3082
3131
|
}
|
|
3083
3132
|
}
|
|
3084
|
-
function LauncherCallout({ callout, launcherPosition, closeLabel, onDismiss }) {
|
|
3133
|
+
function LauncherCallout({ callout, launcherPosition, strings, closeLabel, onDismiss }) {
|
|
3085
3134
|
const [visible, setVisible] = useState(false);
|
|
3086
3135
|
useEffect(() => {
|
|
3087
3136
|
const id = setTimeout(() => setVisible(true), callout.delayMs);
|
|
@@ -3103,7 +3152,7 @@ function LauncherCallout({ callout, launcherPosition, closeLabel, onDismiss }) {
|
|
|
3103
3152
|
"data-animated": callout.animated ? "true" : void 0,
|
|
3104
3153
|
"data-testid": TID.callout,
|
|
3105
3154
|
children: [
|
|
3106
|
-
/* @__PURE__ */ jsx2("span", { children: callout.text }),
|
|
3155
|
+
/* @__PURE__ */ jsx2("span", { children: localizeText(strings, callout.text) }),
|
|
3107
3156
|
/* @__PURE__ */ jsx2(
|
|
3108
3157
|
"button",
|
|
3109
3158
|
{
|
|
@@ -5663,9 +5712,16 @@ function createController(depsRef) {
|
|
|
5663
5712
|
else if (form.frequency !== "always") depsRef.current.persistence.saveFormDone(form.id);
|
|
5664
5713
|
depsRef.current.onComplete(form, trigger, values, skipped);
|
|
5665
5714
|
};
|
|
5715
|
+
const refresh = () => {
|
|
5716
|
+
const active = activeForm.value;
|
|
5717
|
+
if (!active) return;
|
|
5718
|
+
const next = depsRef.current.forms.list.find((f) => f.id === active.form.id);
|
|
5719
|
+
if (next && next !== active.form) activeForm.value = { form: next, trigger: active.trigger };
|
|
5720
|
+
};
|
|
5666
5721
|
return {
|
|
5667
5722
|
activeForm,
|
|
5668
5723
|
fire,
|
|
5724
|
+
refresh,
|
|
5669
5725
|
complete: (values) => finish(values, false),
|
|
5670
5726
|
skip: () => finish({}, true)
|
|
5671
5727
|
};
|
|
@@ -6077,7 +6133,7 @@ function HomeRoot(props2) {
|
|
|
6077
6133
|
const greeting = resolveGreeting(props2);
|
|
6078
6134
|
const avatars = config.userAvatars ?? [];
|
|
6079
6135
|
const status = config.status;
|
|
6080
|
-
const contentTitle = config.contentBlockTitle ?
|
|
6136
|
+
const contentTitle = config.contentBlockTitle ? localizeText(strings, config.contentBlockTitle) : strings.homeContentTitle;
|
|
6081
6137
|
return /* @__PURE__ */ jsx29("div", { class: `${p25}-module ${p25}-home`, "data-testid": TID.homeView, children: /* @__PURE__ */ jsxs24("div", { class: `${p25}-home-scroll`, children: [
|
|
6082
6138
|
/* @__PURE__ */ jsxs24("div", { class: `${p25}-home-hero`, children: [
|
|
6083
6139
|
/* @__PURE__ */ jsxs24("div", { class: `${p25}-home-hero-top`, children: [
|
|
@@ -6232,7 +6288,7 @@ function HomeTabBar({ modules, activeTab, strings, unreadCount, onSelect }) {
|
|
|
6232
6288
|
/* @__PURE__ */ jsx31(Icon, {}),
|
|
6233
6289
|
badge ? /* @__PURE__ */ jsx31("span", { class: `${p27}-tab-badge`, "data-testid": TID.tabBadge, children: badge }) : null
|
|
6234
6290
|
] }),
|
|
6235
|
-
/* @__PURE__ */ jsx31("span", { class: `${p27}-tab-label`, children:
|
|
6291
|
+
/* @__PURE__ */ jsx31("span", { class: `${p27}-tab-label`, children: localizeText(strings, m.label) })
|
|
6236
6292
|
]
|
|
6237
6293
|
},
|
|
6238
6294
|
m.id
|
|
@@ -6903,7 +6959,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6903
6959
|
void (async () => {
|
|
6904
6960
|
try {
|
|
6905
6961
|
const res = await dataBootRef.current;
|
|
6906
|
-
setRemoteForms(resolveForms((res ?? { forms: [] }).forms));
|
|
6962
|
+
setRemoteForms(resolveForms((res ?? { forms: [] }).forms, effectiveLocale));
|
|
6907
6963
|
} catch (err) {
|
|
6908
6964
|
log16.debug("bootstrap failed \u2014 treating as no forms", { err });
|
|
6909
6965
|
} finally {
|
|
@@ -6921,6 +6977,23 @@ function App({ options, hostElement, bus }) {
|
|
|
6921
6977
|
void runResume(handle);
|
|
6922
6978
|
}
|
|
6923
6979
|
}, [activated]);
|
|
6980
|
+
const formsLocaleRef = useRef9(null);
|
|
6981
|
+
useEffect17(() => {
|
|
6982
|
+
if (!activated) return;
|
|
6983
|
+
if (formsLocaleRef.current === null) {
|
|
6984
|
+
formsLocaleRef.current = effectiveLocale;
|
|
6985
|
+
return;
|
|
6986
|
+
}
|
|
6987
|
+
if (formsLocaleRef.current === effectiveLocale) return;
|
|
6988
|
+
formsLocaleRef.current = effectiveLocale;
|
|
6989
|
+
let cancelled = false;
|
|
6990
|
+
void transport.bootstrap().then((res) => {
|
|
6991
|
+
if (!cancelled) setRemoteForms(resolveForms((res ?? { forms: [] }).forms, effectiveLocale));
|
|
6992
|
+
}).catch((err) => log16.debug("forms re-localize failed \u2014 keeping current copy", { err }));
|
|
6993
|
+
return () => {
|
|
6994
|
+
cancelled = true;
|
|
6995
|
+
};
|
|
6996
|
+
}, [activated, effectiveLocale, transport]);
|
|
6924
6997
|
const lastUserSig = useRef9(userSig);
|
|
6925
6998
|
useEffect17(() => {
|
|
6926
6999
|
if (!conversationReady || lastUserSig.current === userSig) return;
|
|
@@ -7077,6 +7150,9 @@ function App({ options, hostElement, bus }) {
|
|
|
7077
7150
|
}
|
|
7078
7151
|
});
|
|
7079
7152
|
const activeForm = useComputed7(() => forms.activeForm.value);
|
|
7153
|
+
useEffect17(() => {
|
|
7154
|
+
forms.refresh();
|
|
7155
|
+
}, [effectiveForms, forms]);
|
|
7080
7156
|
const pageArea = options.pageContext?.area ? String(options.pageContext.area) : void 0;
|
|
7081
7157
|
const msgCount = useComputed7(() => messagesSig.value.length);
|
|
7082
7158
|
useEffect17(() => {
|
|
@@ -7463,6 +7539,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7463
7539
|
{
|
|
7464
7540
|
callout: calloutToRender,
|
|
7465
7541
|
launcherPosition: effectiveOptions.position,
|
|
7542
|
+
strings: effectiveOptions.strings,
|
|
7466
7543
|
closeLabel: effectiveOptions.strings.close,
|
|
7467
7544
|
onDismiss: dismissCallout
|
|
7468
7545
|
}
|
|
@@ -7511,50 +7588,6 @@ var ErrorBoundary = class extends Component {
|
|
|
7511
7588
|
}
|
|
7512
7589
|
};
|
|
7513
7590
|
|
|
7514
|
-
// src/index.ts
|
|
7515
|
-
import { h, render } from "preact";
|
|
7516
|
-
|
|
7517
|
-
// src/shadow/iframe-mount.ts
|
|
7518
|
-
var log18 = logger.scope("iframe");
|
|
7519
|
-
|
|
7520
|
-
// src/core/events.ts
|
|
7521
|
-
var EventBus = class {
|
|
7522
|
-
constructor() {
|
|
7523
|
-
__publicField(this, "handlers", /* @__PURE__ */ new Map());
|
|
7524
|
-
}
|
|
7525
|
-
/**
|
|
7526
|
-
* Register a listener for `name`.
|
|
7527
|
-
* @returns an `off` function that removes this listener.
|
|
7528
|
-
*/
|
|
7529
|
-
on(name, fn) {
|
|
7530
|
-
let set = this.handlers.get(name);
|
|
7531
|
-
if (!set) {
|
|
7532
|
-
set = /* @__PURE__ */ new Set();
|
|
7533
|
-
this.handlers.set(name, set);
|
|
7534
|
-
}
|
|
7535
|
-
set.add(fn);
|
|
7536
|
-
return () => this.handlers.get(name)?.delete(fn);
|
|
7537
|
-
}
|
|
7538
|
-
/**
|
|
7539
|
-
* Dispatch an event synchronously to all listeners.
|
|
7540
|
-
* Listener errors are swallowed — the widget must not crash on user callbacks.
|
|
7541
|
-
*/
|
|
7542
|
-
emit(name, payload) {
|
|
7543
|
-
const set = this.handlers.get(name);
|
|
7544
|
-
if (!set) return;
|
|
7545
|
-
for (const fn of set) {
|
|
7546
|
-
try {
|
|
7547
|
-
fn(payload);
|
|
7548
|
-
} catch {
|
|
7549
|
-
}
|
|
7550
|
-
}
|
|
7551
|
-
}
|
|
7552
|
-
/** Remove every listener. Called by the instance `destroy()`. */
|
|
7553
|
-
clear() {
|
|
7554
|
-
this.handlers.clear();
|
|
7555
|
-
}
|
|
7556
|
-
};
|
|
7557
|
-
|
|
7558
7591
|
// src/core/tracking.ts
|
|
7559
7592
|
var PROTOCOL_VERSION = "1";
|
|
7560
7593
|
var MAX_HITS_PER_MINUTE = 60;
|
|
@@ -7670,10 +7703,60 @@ function createTracker(bus, deps) {
|
|
|
7670
7703
|
};
|
|
7671
7704
|
}
|
|
7672
7705
|
|
|
7673
|
-
// src/core/
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7706
|
+
// src/core/boot.ts
|
|
7707
|
+
function createWidgetRuntime(params) {
|
|
7708
|
+
const { bus, host, appRoot, persistence, reportError } = params;
|
|
7709
|
+
let userRaw = params.initialRaw;
|
|
7710
|
+
let serverConfig;
|
|
7711
|
+
let currentOptions = resolveOptions(userRaw);
|
|
7712
|
+
let firstRender = true;
|
|
7713
|
+
const recompute = () => {
|
|
7714
|
+
currentOptions = resolveOptions(serverConfig ? mergeServerConfig(userRaw, serverConfig) : userRaw);
|
|
7715
|
+
};
|
|
7716
|
+
const applyHostStyling = () => {
|
|
7717
|
+
applyHostPosition(host, hostPositionForMode(currentOptions.mode));
|
|
7718
|
+
const cachedThemeMode = persistence.loadUserPrefs().themeMode;
|
|
7719
|
+
applyHostAttributes(host, cachedThemeMode ? { ...currentOptions, themeMode: cachedThemeMode } : currentOptions);
|
|
7720
|
+
if (firstRender) {
|
|
7721
|
+
applyMode(host, resolveInitialHostMode(currentOptions));
|
|
7722
|
+
firstRender = false;
|
|
7723
|
+
}
|
|
7724
|
+
};
|
|
7725
|
+
const render2 = () => {
|
|
7726
|
+
applyHostStyling();
|
|
7727
|
+
renderPreact(
|
|
7728
|
+
h(ErrorBoundary, { onError: reportError }, h(App, { bus, hostElement: host, options: currentOptions })),
|
|
7729
|
+
appRoot
|
|
7730
|
+
);
|
|
7731
|
+
};
|
|
7732
|
+
bus.on("handshake", (response) => {
|
|
7733
|
+
if (!response.config) return;
|
|
7734
|
+
serverConfig = response.config;
|
|
7735
|
+
recompute();
|
|
7736
|
+
render2();
|
|
7737
|
+
});
|
|
7738
|
+
const untrack = createTracker(bus, {
|
|
7739
|
+
getOptions: () => currentOptions,
|
|
7740
|
+
getVisitorId: () => persistence.getVisitorId(),
|
|
7741
|
+
getConversationId: () => persistence.loadConversationId()
|
|
7742
|
+
});
|
|
7743
|
+
return {
|
|
7744
|
+
getOptions: () => currentOptions,
|
|
7745
|
+
getUserOptions: () => userRaw,
|
|
7746
|
+
render: render2,
|
|
7747
|
+
setUserOptions: (raw) => {
|
|
7748
|
+
userRaw = raw;
|
|
7749
|
+
recompute();
|
|
7750
|
+
render2();
|
|
7751
|
+
},
|
|
7752
|
+
patchUserOptions: (patch) => {
|
|
7753
|
+
userRaw = { ...userRaw, ...patch };
|
|
7754
|
+
recompute();
|
|
7755
|
+
render2();
|
|
7756
|
+
},
|
|
7757
|
+
destroy: () => untrack()
|
|
7758
|
+
};
|
|
7759
|
+
}
|
|
7677
7760
|
function resolveInitialHostMode(options) {
|
|
7678
7761
|
if (options.mode === "standalone") return "standalone";
|
|
7679
7762
|
if (options.mode === "inline") return "inline";
|
|
@@ -7687,39 +7770,52 @@ function resolveInitialHostMode(options) {
|
|
|
7687
7770
|
return computeHostMode(options.mode, panelSize, panelOpen);
|
|
7688
7771
|
}
|
|
7689
7772
|
|
|
7773
|
+
// src/core/events.ts
|
|
7774
|
+
var EventBus = class {
|
|
7775
|
+
constructor() {
|
|
7776
|
+
__publicField(this, "handlers", /* @__PURE__ */ new Map());
|
|
7777
|
+
}
|
|
7778
|
+
/**
|
|
7779
|
+
* Register a listener for `name`.
|
|
7780
|
+
* @returns an `off` function that removes this listener.
|
|
7781
|
+
*/
|
|
7782
|
+
on(name, fn) {
|
|
7783
|
+
let set = this.handlers.get(name);
|
|
7784
|
+
if (!set) {
|
|
7785
|
+
set = /* @__PURE__ */ new Set();
|
|
7786
|
+
this.handlers.set(name, set);
|
|
7787
|
+
}
|
|
7788
|
+
set.add(fn);
|
|
7789
|
+
return () => this.handlers.get(name)?.delete(fn);
|
|
7790
|
+
}
|
|
7791
|
+
/**
|
|
7792
|
+
* Dispatch an event synchronously to all listeners.
|
|
7793
|
+
* Listener errors are swallowed — the widget must not crash on user callbacks.
|
|
7794
|
+
*/
|
|
7795
|
+
emit(name, payload) {
|
|
7796
|
+
const set = this.handlers.get(name);
|
|
7797
|
+
if (!set) return;
|
|
7798
|
+
for (const fn of set) {
|
|
7799
|
+
try {
|
|
7800
|
+
fn(payload);
|
|
7801
|
+
} catch {
|
|
7802
|
+
}
|
|
7803
|
+
}
|
|
7804
|
+
}
|
|
7805
|
+
/** Remove every listener. Called by the instance `destroy()`. */
|
|
7806
|
+
clear() {
|
|
7807
|
+
this.handlers.clear();
|
|
7808
|
+
}
|
|
7809
|
+
};
|
|
7810
|
+
|
|
7690
7811
|
// src/web-component.ts
|
|
7691
7812
|
var ChatElement = class extends HTMLElement {
|
|
7692
7813
|
constructor() {
|
|
7693
7814
|
super(...arguments);
|
|
7694
7815
|
__publicField(this, "bus", new EventBus());
|
|
7695
7816
|
__publicField(this, "mountResult", null);
|
|
7696
|
-
/**
|
|
7697
|
-
|
|
7698
|
-
* HTML attributes; gets server-side settings merged in on the
|
|
7699
|
-
* `handshake` event so subsequent re-renders pick up the server's
|
|
7700
|
-
* mode / theme / launcher / etc.
|
|
7701
|
-
*
|
|
7702
|
-
* Mirrors `index.ts`'s `rawOptions` so both entry points share the
|
|
7703
|
-
* same user-wins-per-field merge policy.
|
|
7704
|
-
*/
|
|
7705
|
-
__publicField(this, "rawOptions", {});
|
|
7706
|
-
/**
|
|
7707
|
-
* Latest resolved options — refreshed every `renderApp()` so the usage
|
|
7708
|
-
* tracker (created once in `boot`) reads live, post-handshake-merge
|
|
7709
|
-
* values (`tracking.enabled` / `endpoint` / `token`).
|
|
7710
|
-
*
|
|
7711
|
-
* This path is independent of `mount()` (it supports N elements per page
|
|
7712
|
-
* without a shared `widgetId` registry), so everything `mount()` composes
|
|
7713
|
-
* around `<App>` must be mirrored here: the handshake-config merge, the
|
|
7714
|
-
* usage tracker, and the ErrorBoundary wrapper. This has drifted three
|
|
7715
|
-
* times now (mode-merge, tracker, ErrorBoundary). The proper fix is to
|
|
7716
|
-
* extract the shared `<App>` composition into one `bootWidget(opts,
|
|
7717
|
-
* { hostElement })` helper both entry points call — tracked as a
|
|
7718
|
-
* follow-up; until then, keep these two boot paths in lockstep.
|
|
7719
|
-
*/
|
|
7720
|
-
__publicField(this, "resolved", null);
|
|
7721
|
-
/** Tracker unsubscribe — released on disconnect. */
|
|
7722
|
-
__publicField(this, "untrack", null);
|
|
7817
|
+
/** Live widget composition — null between disconnect and the next connect. */
|
|
7818
|
+
__publicField(this, "runtime", null);
|
|
7723
7819
|
}
|
|
7724
7820
|
static get observedAttributes() {
|
|
7725
7821
|
return REACTIVE_ATTRS;
|
|
@@ -7729,57 +7825,37 @@ var ChatElement = class extends HTMLElement {
|
|
|
7729
7825
|
}
|
|
7730
7826
|
disconnectedCallback() {
|
|
7731
7827
|
if (!this.mountResult) return;
|
|
7732
|
-
this.
|
|
7733
|
-
this.
|
|
7734
|
-
|
|
7828
|
+
this.runtime?.destroy();
|
|
7829
|
+
this.runtime = null;
|
|
7830
|
+
render(null, this.mountResult.appRoot);
|
|
7735
7831
|
this.bus.clear();
|
|
7736
7832
|
}
|
|
7737
7833
|
attributeChangedCallback() {
|
|
7738
7834
|
if (this.isConnected) this.boot();
|
|
7739
7835
|
}
|
|
7740
7836
|
boot() {
|
|
7741
|
-
|
|
7837
|
+
const rawOptions = parseAttributes((name) => this.getAttribute(name));
|
|
7838
|
+
if (this.runtime) {
|
|
7839
|
+
this.runtime.setUserOptions(rawOptions);
|
|
7840
|
+
return;
|
|
7841
|
+
}
|
|
7842
|
+
const resolved = resolveOptions(rawOptions);
|
|
7742
7843
|
if (!this.mountResult) {
|
|
7743
|
-
const resolved = resolveOptions(this.rawOptions);
|
|
7744
7844
|
this.mountResult = createShadowMount({
|
|
7745
7845
|
existingHost: this,
|
|
7746
7846
|
position: hostPositionForMode(resolved.mode)
|
|
7747
7847
|
});
|
|
7748
|
-
this.bus.on("handshake", (response) => {
|
|
7749
|
-
if (!response.config) return;
|
|
7750
|
-
this.rawOptions = mergeServerConfig(this.rawOptions, response.config);
|
|
7751
|
-
this.renderApp();
|
|
7752
|
-
});
|
|
7753
|
-
const persistence = createPersistence(resolved.widgetId, resolved.storage);
|
|
7754
|
-
this.untrack = createTracker(this.bus, {
|
|
7755
|
-
getOptions: () => this.resolved ?? resolved,
|
|
7756
|
-
getVisitorId: () => persistence.getVisitorId(),
|
|
7757
|
-
getConversationId: () => persistence.loadConversationId()
|
|
7758
|
-
});
|
|
7759
7848
|
}
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
this.
|
|
7770
|
-
const host = this.mountResult.hostElement;
|
|
7771
|
-
applyHostPosition(host, hostPositionForMode(resolved.mode));
|
|
7772
|
-
const cachedThemeMode = createPersistence(resolved.widgetId, resolved.storage).loadUserPrefs().themeMode;
|
|
7773
|
-
applyHostAttributes(host, cachedThemeMode ? { ...resolved, themeMode: cachedThemeMode } : resolved);
|
|
7774
|
-
applyMode(host, resolveInitialHostMode(resolved));
|
|
7775
|
-
render2(
|
|
7776
|
-
h2(
|
|
7777
|
-
ErrorBoundary,
|
|
7778
|
-
{ onError: (error) => this.bus.emit("error", error) },
|
|
7779
|
-
h2(App, { bus: this.bus, hostElement: host, options: resolved })
|
|
7780
|
-
),
|
|
7781
|
-
this.mountResult.appRoot
|
|
7782
|
-
);
|
|
7849
|
+
const persistence = createPersistence(resolved.widgetId, resolved.storage);
|
|
7850
|
+
this.runtime = createWidgetRuntime({
|
|
7851
|
+
bus: this.bus,
|
|
7852
|
+
host: this.mountResult.hostElement,
|
|
7853
|
+
appRoot: this.mountResult.appRoot,
|
|
7854
|
+
persistence,
|
|
7855
|
+
initialRaw: rawOptions,
|
|
7856
|
+
reportError: (error) => this.bus.emit("error", error)
|
|
7857
|
+
});
|
|
7858
|
+
this.runtime.render();
|
|
7783
7859
|
}
|
|
7784
7860
|
};
|
|
7785
7861
|
var registered = false;
|