@helpai/elements 0.30.0 → 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 +27 -27
- package/elements-web-component.esm.js.map +4 -4
- package/elements.cjs.js +18 -18
- package/elements.cjs.js.map +4 -4
- package/elements.esm.js +18 -18
- package/elements.esm.js.map +4 -4
- package/elements.js +19 -19
- package/elements.js.map +4 -4
- package/index.d.ts +47 -26
- package/index.mjs +554 -524
- package/package.json +1 -1
- package/schema.d.ts +1 -1
- package/web-component.d.ts +11 -5
- package/web-component.mjs +335 -333
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 = {
|
|
@@ -993,35 +989,153 @@ function parseLauncher(get, str2) {
|
|
|
993
989
|
return Object.keys(base).length > 0 ? base : null;
|
|
994
990
|
}
|
|
995
991
|
|
|
996
|
-
// src/core/
|
|
997
|
-
function
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
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)}`;
|
|
1002
1007
|
}
|
|
1003
|
-
function
|
|
1004
|
-
if (
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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);
|
|
1008
1014
|
}
|
|
1009
1015
|
|
|
1010
|
-
// src/core/
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
const
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
|
|
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
|
+
}
|
|
1024
1134
|
};
|
|
1135
|
+
return persistence;
|
|
1136
|
+
}
|
|
1137
|
+
function isPlainObject(v) {
|
|
1138
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1025
1139
|
}
|
|
1026
1140
|
|
|
1027
1141
|
// src/styles/tokens.css
|
|
@@ -1048,7 +1162,7 @@ var FONT = true ? "Inter, system-ui, -apple-system, 'Segoe UI', sans-serif" : "I
|
|
|
1048
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);
|
|
1049
1163
|
|
|
1050
1164
|
// src/shadow/mount.ts
|
|
1051
|
-
var
|
|
1165
|
+
var log3 = logger.scope("mount");
|
|
1052
1166
|
var sheetsByDocument = /* @__PURE__ */ new WeakMap();
|
|
1053
1167
|
var HOST_CSS = {
|
|
1054
1168
|
floating: "all: initial; position: fixed; z-index: 2147483600;",
|
|
@@ -1065,7 +1179,7 @@ function hostPositionForMode(mode) {
|
|
|
1065
1179
|
}
|
|
1066
1180
|
function createShadowMount(opts = {}) {
|
|
1067
1181
|
const position = opts.position ?? "flow";
|
|
1068
|
-
|
|
1182
|
+
log3.debug("createShadowMount", { position, hasTarget: !!opts.target, hasHost: !!opts.existingHost });
|
|
1069
1183
|
const tagName = `${BRAND.cssPrefix}-chat-host`;
|
|
1070
1184
|
if (typeof customElements !== "undefined" && !customElements.get(tagName)) {
|
|
1071
1185
|
customElements.define(tagName, class extends HTMLElement {
|
|
@@ -1149,6 +1263,44 @@ function applyHostAttributes(host, resolved) {
|
|
|
1149
1263
|
applySize(host, resolved.size);
|
|
1150
1264
|
}
|
|
1151
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
|
+
|
|
1152
1304
|
// src/ui/host-mode.ts
|
|
1153
1305
|
function computeHostMode(configured, panelSize, isOpen) {
|
|
1154
1306
|
if (configured === "page") return "page";
|
|
@@ -1199,30 +1351,6 @@ function createAuth(opts) {
|
|
|
1199
1351
|
};
|
|
1200
1352
|
}
|
|
1201
1353
|
|
|
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
1354
|
// src/stream/types.ts
|
|
1227
1355
|
var StreamError = class extends Error {
|
|
1228
1356
|
constructor(message, code, status, cause) {
|
|
@@ -1235,7 +1363,7 @@ var StreamError = class extends Error {
|
|
|
1235
1363
|
};
|
|
1236
1364
|
|
|
1237
1365
|
// src/stream/parser.ts
|
|
1238
|
-
var
|
|
1366
|
+
var log4 = logger.scope("parser");
|
|
1239
1367
|
async function* parseChatStream(response, signal7) {
|
|
1240
1368
|
if (!response.ok) {
|
|
1241
1369
|
const text = await response.text().catch(() => "");
|
|
@@ -1287,10 +1415,10 @@ ${part}` : part;
|
|
|
1287
1415
|
if (!dataPayload || dataPayload === "[DONE]") return null;
|
|
1288
1416
|
try {
|
|
1289
1417
|
const chunk = JSON.parse(dataPayload);
|
|
1290
|
-
|
|
1418
|
+
log4.trace("chunk", { type: chunk.type, eventId });
|
|
1291
1419
|
return { chunk, eventId };
|
|
1292
1420
|
} catch (err) {
|
|
1293
|
-
|
|
1421
|
+
log4.trace("frame parse failed", { err, payload: dataPayload.slice(0, 200) });
|
|
1294
1422
|
return null;
|
|
1295
1423
|
}
|
|
1296
1424
|
}
|
|
@@ -1445,7 +1573,7 @@ function messageToWireParts(m) {
|
|
|
1445
1573
|
}
|
|
1446
1574
|
|
|
1447
1575
|
// src/stream/transport.ts
|
|
1448
|
-
var
|
|
1576
|
+
var log5 = logger.scope("transport");
|
|
1449
1577
|
var MAX_RESUME_ATTEMPTS = 3;
|
|
1450
1578
|
var RESUME_BACKOFF_MS = 400;
|
|
1451
1579
|
var CONTENT_CACHE_TTL_MS = 6e4;
|
|
@@ -1571,13 +1699,13 @@ var AgentTransport = class {
|
|
|
1571
1699
|
}
|
|
1572
1700
|
/** One-shot runtime bootstrap. Called once after the widget mounts. */
|
|
1573
1701
|
async handshake(body) {
|
|
1574
|
-
|
|
1702
|
+
log5.debug("handshake \u2192", {
|
|
1575
1703
|
visitorId: body.visitorId,
|
|
1576
1704
|
conversationId: body.conversationId,
|
|
1577
1705
|
locale: body.locale
|
|
1578
1706
|
});
|
|
1579
1707
|
const res = await this.postJson(DEFAULT_PATHS.handshake, body, "handshake");
|
|
1580
|
-
|
|
1708
|
+
log5.debug("handshake \u2190", {
|
|
1581
1709
|
visitorId: res.visitorId,
|
|
1582
1710
|
conversationId: res.conversationId,
|
|
1583
1711
|
hasWelcome: !!res.welcome,
|
|
@@ -1585,7 +1713,7 @@ var AgentTransport = class {
|
|
|
1585
1713
|
rebind: res.rebind
|
|
1586
1714
|
});
|
|
1587
1715
|
if (!isHandshakeResponseShape(res)) {
|
|
1588
|
-
|
|
1716
|
+
log5.warn("handshake response did not match expected shape; using raw response", { response: res });
|
|
1589
1717
|
}
|
|
1590
1718
|
this.visitorId = res.visitorId ?? body.visitorId;
|
|
1591
1719
|
this.conversationId = res.conversationId;
|
|
@@ -1593,11 +1721,11 @@ var AgentTransport = class {
|
|
|
1593
1721
|
}
|
|
1594
1722
|
/** Upload a file attachment. Returns the `{id, url}` the backend assigns. */
|
|
1595
1723
|
async uploadFile(file) {
|
|
1596
|
-
|
|
1724
|
+
log5.debug("uploadFile \u2192", { name: file.name, size: file.size, type: file.type });
|
|
1597
1725
|
const fd = new FormData();
|
|
1598
1726
|
fd.append("file", file, file.name);
|
|
1599
1727
|
const res = await this.postForm(this.uploadPath, fd, "upload");
|
|
1600
|
-
|
|
1728
|
+
log5.debug("uploadFile \u2190", { id: res.id, url: res.url });
|
|
1601
1729
|
return res;
|
|
1602
1730
|
}
|
|
1603
1731
|
/**
|
|
@@ -1605,12 +1733,12 @@ var AgentTransport = class {
|
|
|
1605
1733
|
* Only used when `features.voice === 'server'`.
|
|
1606
1734
|
*/
|
|
1607
1735
|
async transcribe(audio, mimeType = "audio/webm") {
|
|
1608
|
-
|
|
1736
|
+
log5.debug("transcribe \u2192", { size: audio.size, mimeType });
|
|
1609
1737
|
const fd = new FormData();
|
|
1610
1738
|
fd.append("audio", audio, `voice.${extensionFor(mimeType)}`);
|
|
1611
1739
|
fd.append("mimeType", mimeType);
|
|
1612
1740
|
const res = await this.postForm(this.transcribePath, fd, "transcribe");
|
|
1613
|
-
|
|
1741
|
+
log5.debug("transcribe \u2190", { textLen: res.text.length });
|
|
1614
1742
|
return res;
|
|
1615
1743
|
}
|
|
1616
1744
|
async listConversations(params = {}) {
|
|
@@ -1618,14 +1746,14 @@ var AgentTransport = class {
|
|
|
1618
1746
|
if (params.visitorId) url.searchParams.set("visitorId", params.visitorId);
|
|
1619
1747
|
if (params.limit) url.searchParams.set("limit", String(params.limit));
|
|
1620
1748
|
if (params.cursor) url.searchParams.set("cursor", params.cursor);
|
|
1621
|
-
|
|
1749
|
+
log5.debug("listConversations \u2192", {
|
|
1622
1750
|
visitorId: params.visitorId ?? this.visitorId,
|
|
1623
1751
|
limit: params.limit,
|
|
1624
1752
|
cursor: params.cursor
|
|
1625
1753
|
});
|
|
1626
1754
|
const res = await this.getJson(url.toString(), "listConversations");
|
|
1627
1755
|
const conversations = res.conversations ?? [];
|
|
1628
|
-
|
|
1756
|
+
log5.debug("listConversations \u2190", { count: conversations.length, nextCursor: res.nextCursor });
|
|
1629
1757
|
return {
|
|
1630
1758
|
conversations: conversations.map((conversation) => ({
|
|
1631
1759
|
conversationId: conversation.conversationId,
|
|
@@ -1641,10 +1769,10 @@ var AgentTransport = class {
|
|
|
1641
1769
|
async loadConversation(conversationId) {
|
|
1642
1770
|
const url = new URL(this.url(DEFAULT_PATHS.listMessages));
|
|
1643
1771
|
url.searchParams.set("conversationId", conversationId);
|
|
1644
|
-
|
|
1772
|
+
log5.debug("loadConversation \u2192", { conversationId, visitorId: this.visitorId });
|
|
1645
1773
|
const res = await this.getJson(url.toString(), "loadConversation");
|
|
1646
1774
|
const messages = res.messages ?? [];
|
|
1647
|
-
|
|
1775
|
+
log5.debug("loadConversation \u2190", {
|
|
1648
1776
|
conversationId: res.conversationId,
|
|
1649
1777
|
canContinue: res.canContinue,
|
|
1650
1778
|
messageCount: messages.length,
|
|
@@ -1666,11 +1794,11 @@ var AgentTransport = class {
|
|
|
1666
1794
|
*/
|
|
1667
1795
|
async markRead(conversationId) {
|
|
1668
1796
|
if (!this.visitorId || !conversationId) return;
|
|
1669
|
-
|
|
1797
|
+
log5.debug("markRead \u2192", { conversationId, visitorId: this.visitorId });
|
|
1670
1798
|
try {
|
|
1671
1799
|
await this.postJson(DEFAULT_PATHS.markRead, { conversationId }, "markRead");
|
|
1672
1800
|
} catch (err) {
|
|
1673
|
-
|
|
1801
|
+
log5.debug("markRead failed (non-fatal)", { err });
|
|
1674
1802
|
}
|
|
1675
1803
|
}
|
|
1676
1804
|
/** Fetch content rows by filter. `GET /pai/content` (data-API), TTL-cached. */
|
|
@@ -1678,7 +1806,7 @@ var AgentTransport = class {
|
|
|
1678
1806
|
const key = JSON.stringify(query, Object.keys(query).toSorted());
|
|
1679
1807
|
const cached = this.contentCache.get(key);
|
|
1680
1808
|
if (cached && Date.now() - cached.at < CONTENT_CACHE_TTL_MS) {
|
|
1681
|
-
|
|
1809
|
+
log5.debug("listContent \u2192 cache", query);
|
|
1682
1810
|
return cached.result;
|
|
1683
1811
|
}
|
|
1684
1812
|
const result = this.fetchContent(query).catch((err) => {
|
|
@@ -1694,10 +1822,10 @@ var AgentTransport = class {
|
|
|
1694
1822
|
if (value === void 0) continue;
|
|
1695
1823
|
url.searchParams.set(key, Array.isArray(value) ? value.join(",") : String(value));
|
|
1696
1824
|
}
|
|
1697
|
-
|
|
1825
|
+
log5.debug("listContent \u2192", query);
|
|
1698
1826
|
const res = await this.getJson(url.toString(), "listContent");
|
|
1699
1827
|
const items = res.items ?? [];
|
|
1700
|
-
|
|
1828
|
+
log5.debug("listContent \u2190", { count: items.length, total: res.pagination?.total });
|
|
1701
1829
|
return { ...res, items };
|
|
1702
1830
|
}
|
|
1703
1831
|
/**
|
|
@@ -1715,21 +1843,21 @@ var AgentTransport = class {
|
|
|
1715
1843
|
async bootstrap(conversationId) {
|
|
1716
1844
|
const url = new URL(this.dataUrl(DEFAULT_PATHS.bootstrap));
|
|
1717
1845
|
if (conversationId) url.searchParams.set("conversationId", conversationId);
|
|
1718
|
-
|
|
1846
|
+
log5.debug("bootstrap \u2192", { conversationId: conversationId ?? this.conversationId });
|
|
1719
1847
|
const res = await this.getJson(url.toString(), "bootstrap");
|
|
1720
1848
|
const forms = res.forms ?? [];
|
|
1721
1849
|
const formSubmissions = res.formSubmissions ?? [];
|
|
1722
|
-
|
|
1850
|
+
log5.debug("bootstrap \u2190", { forms: forms.length, formSubmissions: formSubmissions.length });
|
|
1723
1851
|
return { forms, formSubmissions };
|
|
1724
1852
|
}
|
|
1725
1853
|
async saveUserPrefs(userPrefs) {
|
|
1726
|
-
|
|
1854
|
+
log5.debug("saveUserPrefs \u2192", {
|
|
1727
1855
|
visitorId: this.visitorId,
|
|
1728
1856
|
conversationId: this.conversationId,
|
|
1729
1857
|
settingsKeys: Object.keys(userPrefs)
|
|
1730
1858
|
});
|
|
1731
1859
|
const res = await this.postJson(DEFAULT_PATHS.updateSettings, { userPrefs }, "saveUserPrefs");
|
|
1732
|
-
|
|
1860
|
+
log5.debug("saveUserPrefs \u2190", { ok: res.ok });
|
|
1733
1861
|
return res;
|
|
1734
1862
|
}
|
|
1735
1863
|
get uploadPath() {
|
|
@@ -1750,15 +1878,15 @@ var AgentTransport = class {
|
|
|
1750
1878
|
* that doesn't implement the endpoint) is non-fatal, so callers don't await.
|
|
1751
1879
|
*/
|
|
1752
1880
|
async submitForm(body) {
|
|
1753
|
-
|
|
1881
|
+
log5.debug("submitForm \u2192", { formId: body.formId, trigger: body.trigger, fields: Object.keys(body.values).length });
|
|
1754
1882
|
try {
|
|
1755
1883
|
await this.postJson(this.dataUrl(this.submitFormPath), body, "submitForm");
|
|
1756
1884
|
} catch (err) {
|
|
1757
|
-
|
|
1885
|
+
log5.debug("submitForm failed (non-fatal)", { err });
|
|
1758
1886
|
}
|
|
1759
1887
|
}
|
|
1760
1888
|
sendMessage(body) {
|
|
1761
|
-
|
|
1889
|
+
log5.debug("message \u2192", {
|
|
1762
1890
|
conversationId: body.conversationId,
|
|
1763
1891
|
visitorId: body.visitorId,
|
|
1764
1892
|
messageCount: body.messages.length,
|
|
@@ -1838,17 +1966,17 @@ var AgentTransport = class {
|
|
|
1838
1966
|
});
|
|
1839
1967
|
} catch (err) {
|
|
1840
1968
|
if (ctrl.signal.aborted) return;
|
|
1841
|
-
|
|
1969
|
+
log5.debug("resume fetch failed \u2014 retrying", { attempt, err });
|
|
1842
1970
|
continue;
|
|
1843
1971
|
}
|
|
1844
1972
|
if (res.status === 204 || !res.ok) {
|
|
1845
|
-
|
|
1973
|
+
log5.debug("resume \u2192 no in-flight stream", { status: res.status });
|
|
1846
1974
|
return;
|
|
1847
1975
|
}
|
|
1848
1976
|
const before = seenIds.size;
|
|
1849
1977
|
if (yield* this.drain(parseChatStream(res, ctrl.signal), seenIds, ctrl)) return;
|
|
1850
1978
|
if (seenIds.size === before) {
|
|
1851
|
-
|
|
1979
|
+
log5.debug("resume \u2192 stream closed with no new events; nothing to resume");
|
|
1852
1980
|
return;
|
|
1853
1981
|
}
|
|
1854
1982
|
}
|
|
@@ -1875,14 +2003,14 @@ var AgentTransport = class {
|
|
|
1875
2003
|
} catch (err) {
|
|
1876
2004
|
if (ctrl.signal.aborted) return true;
|
|
1877
2005
|
if (err instanceof StreamError && err.status !== void 0) throw err;
|
|
1878
|
-
|
|
2006
|
+
log5.debug("stream segment dropped", { err });
|
|
1879
2007
|
}
|
|
1880
2008
|
return false;
|
|
1881
2009
|
}
|
|
1882
2010
|
/** Abort + fire-and-forget POST to `/pai/cancel-stream`. Idempotent. */
|
|
1883
2011
|
cancelStream(ctrl, conversationId) {
|
|
1884
2012
|
if (ctrl.signal.aborted) return;
|
|
1885
|
-
|
|
2013
|
+
log5.debug("cancel stream", { conversationId, visitorId: this.visitorId });
|
|
1886
2014
|
ctrl.abort();
|
|
1887
2015
|
if (!this.visitorId || !conversationId) return;
|
|
1888
2016
|
this.fetchImpl(this.url(DEFAULT_PATHS.cancelStream), {
|
|
@@ -1891,7 +2019,7 @@ var AgentTransport = class {
|
|
|
1891
2019
|
headers: { "content-type": "application/json", ...this.opts.auth.headers() },
|
|
1892
2020
|
body: JSON.stringify(this.withEnvelope({})),
|
|
1893
2021
|
keepalive: true
|
|
1894
|
-
}).catch((err) =>
|
|
2022
|
+
}).catch((err) => log5.debug("cancel POST failed (non-fatal)", { err }));
|
|
1895
2023
|
}
|
|
1896
2024
|
// ---- Low-level fetch helpers ------------------------------------------
|
|
1897
2025
|
async *openMessageStream(body, signal7) {
|
|
@@ -1973,13 +2101,13 @@ var AgentTransport = class {
|
|
|
1973
2101
|
if (attempt >= MAX_REQUEST_RETRIES) {
|
|
1974
2102
|
throw new StreamError(`${label} failed: network error`, "network", void 0, err);
|
|
1975
2103
|
}
|
|
1976
|
-
|
|
2104
|
+
log5.debug("request network error \u2014 retrying", { label, attempt });
|
|
1977
2105
|
await sleep(backoffMs(attempt));
|
|
1978
2106
|
continue;
|
|
1979
2107
|
}
|
|
1980
2108
|
if (res.ok || !RETRYABLE_STATUS.has(res.status) || attempt >= MAX_REQUEST_RETRIES) return res;
|
|
1981
2109
|
const wait = retryAfterMs(res.headers) ?? backoffMs(attempt);
|
|
1982
|
-
|
|
2110
|
+
log5.debug("request retryable status \u2014 retrying", { label, status: res.status, attempt, wait });
|
|
1983
2111
|
await sleep(wait);
|
|
1984
2112
|
}
|
|
1985
2113
|
}
|
|
@@ -2174,7 +2302,7 @@ var TRIGGER = {
|
|
|
2174
2302
|
};
|
|
2175
2303
|
|
|
2176
2304
|
// src/stream/reducer.ts
|
|
2177
|
-
var
|
|
2305
|
+
var log6 = logger.scope("reducer");
|
|
2178
2306
|
var StreamReducer = class {
|
|
2179
2307
|
constructor(messagesSig) {
|
|
2180
2308
|
__publicField(this, "messagesSig", messagesSig);
|
|
@@ -2196,13 +2324,13 @@ var StreamReducer = class {
|
|
|
2196
2324
|
case "finish-step":
|
|
2197
2325
|
return;
|
|
2198
2326
|
case "finish":
|
|
2199
|
-
|
|
2327
|
+
log6.debug("finish", { reason: chunk.finishReason, canContinue: chunk.canContinue });
|
|
2200
2328
|
m.status = "complete";
|
|
2201
2329
|
if (chunk.finishReason) m.finishReason = chunk.finishReason;
|
|
2202
2330
|
this.bumpDone(m);
|
|
2203
2331
|
return;
|
|
2204
2332
|
case "error":
|
|
2205
|
-
|
|
2333
|
+
log6.warn("stream error chunk", { errorText: chunk.errorText });
|
|
2206
2334
|
m.status = "error";
|
|
2207
2335
|
m.errorText = chunk.errorText;
|
|
2208
2336
|
this.bumpDone(m);
|
|
@@ -2331,131 +2459,6 @@ function applyTool(m, chunk) {
|
|
|
2331
2459
|
}
|
|
2332
2460
|
}
|
|
2333
2461
|
|
|
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
2462
|
// src/core/feedback/index.ts
|
|
2460
2463
|
import { signal as signal3 } from "@preact/signals";
|
|
2461
2464
|
|
|
@@ -7511,50 +7514,6 @@ var ErrorBoundary = class extends Component {
|
|
|
7511
7514
|
}
|
|
7512
7515
|
};
|
|
7513
7516
|
|
|
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
7517
|
// src/core/tracking.ts
|
|
7559
7518
|
var PROTOCOL_VERSION = "1";
|
|
7560
7519
|
var MAX_HITS_PER_MINUTE = 60;
|
|
@@ -7670,10 +7629,60 @@ function createTracker(bus, deps) {
|
|
|
7670
7629
|
};
|
|
7671
7630
|
}
|
|
7672
7631
|
|
|
7673
|
-
// src/core/
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
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
|
+
}
|
|
7677
7686
|
function resolveInitialHostMode(options) {
|
|
7678
7687
|
if (options.mode === "standalone") return "standalone";
|
|
7679
7688
|
if (options.mode === "inline") return "inline";
|
|
@@ -7687,39 +7696,52 @@ function resolveInitialHostMode(options) {
|
|
|
7687
7696
|
return computeHostMode(options.mode, panelSize, panelOpen);
|
|
7688
7697
|
}
|
|
7689
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
|
+
|
|
7690
7737
|
// src/web-component.ts
|
|
7691
7738
|
var ChatElement = class extends HTMLElement {
|
|
7692
7739
|
constructor() {
|
|
7693
7740
|
super(...arguments);
|
|
7694
7741
|
__publicField(this, "bus", new EventBus());
|
|
7695
7742
|
__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);
|
|
7743
|
+
/** Live widget composition — null between disconnect and the next connect. */
|
|
7744
|
+
__publicField(this, "runtime", null);
|
|
7723
7745
|
}
|
|
7724
7746
|
static get observedAttributes() {
|
|
7725
7747
|
return REACTIVE_ATTRS;
|
|
@@ -7729,57 +7751,37 @@ var ChatElement = class extends HTMLElement {
|
|
|
7729
7751
|
}
|
|
7730
7752
|
disconnectedCallback() {
|
|
7731
7753
|
if (!this.mountResult) return;
|
|
7732
|
-
this.
|
|
7733
|
-
this.
|
|
7734
|
-
|
|
7754
|
+
this.runtime?.destroy();
|
|
7755
|
+
this.runtime = null;
|
|
7756
|
+
render(null, this.mountResult.appRoot);
|
|
7735
7757
|
this.bus.clear();
|
|
7736
7758
|
}
|
|
7737
7759
|
attributeChangedCallback() {
|
|
7738
7760
|
if (this.isConnected) this.boot();
|
|
7739
7761
|
}
|
|
7740
7762
|
boot() {
|
|
7741
|
-
|
|
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);
|
|
7742
7769
|
if (!this.mountResult) {
|
|
7743
|
-
const resolved = resolveOptions(this.rawOptions);
|
|
7744
7770
|
this.mountResult = createShadowMount({
|
|
7745
7771
|
existingHost: this,
|
|
7746
7772
|
position: hostPositionForMode(resolved.mode)
|
|
7747
7773
|
});
|
|
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
7774
|
}
|
|
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
|
-
);
|
|
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();
|
|
7783
7785
|
}
|
|
7784
7786
|
};
|
|
7785
7787
|
var registered = false;
|