@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/index.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/index.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) {
|
|
@@ -556,7 +597,7 @@ function resolveOptions(rawOpts) {
|
|
|
556
597
|
humanInLoop: opts.features?.humanInLoop ?? DEFAULT_FEATURES.humanInLoop,
|
|
557
598
|
tools: opts.features?.tools
|
|
558
599
|
},
|
|
559
|
-
forms: resolveForms(opts.forms),
|
|
600
|
+
forms: resolveForms(opts.forms, locale),
|
|
560
601
|
endpoints: {
|
|
561
602
|
upload: opts.endpoints?.upload ?? DEFAULT_ENDPOINTS.upload,
|
|
562
603
|
transcribe: opts.endpoints?.transcribe ?? DEFAULT_ENDPOINTS.transcribe,
|
|
@@ -679,13 +720,14 @@ function resolveModules(overrides) {
|
|
|
679
720
|
const byId = Object.fromEntries(list.map((m) => [m.id, m]));
|
|
680
721
|
return { list, byId };
|
|
681
722
|
}
|
|
682
|
-
function resolveForms(overrides) {
|
|
723
|
+
function resolveForms(overrides, locale) {
|
|
683
724
|
var _a;
|
|
684
725
|
if (!overrides?.length) return DEFAULT_FORMS;
|
|
685
726
|
const list = [];
|
|
686
727
|
const seen = /* @__PURE__ */ new Set();
|
|
687
|
-
for (const
|
|
688
|
-
if (!
|
|
728
|
+
for (const raw of overrides) {
|
|
729
|
+
if (!raw?.id || seen.has(raw.id)) continue;
|
|
730
|
+
const def = overlayFormDef(raw, locale);
|
|
689
731
|
const fields = (def.fields ?? []).filter((f) => Boolean(f && f.name && f.label && f.type));
|
|
690
732
|
const triggers = (Array.isArray(def.on) ? def.on : [def.on]).map(parseTrigger).filter((t) => t !== null);
|
|
691
733
|
if (fields.length === 0 || triggers.length === 0) continue;
|
|
@@ -935,20 +977,6 @@ var REACTIVE_ATTRS = [
|
|
|
935
977
|
...SPECIAL_REACTIVE_ATTRS
|
|
936
978
|
];
|
|
937
979
|
|
|
938
|
-
// src/core/handshake-shape.ts
|
|
939
|
-
function isPlainObject(raw) {
|
|
940
|
-
return !!raw && typeof raw === "object" && !Array.isArray(raw);
|
|
941
|
-
}
|
|
942
|
-
function isSiteConfigShape(raw) {
|
|
943
|
-
return isPlainObject(raw);
|
|
944
|
-
}
|
|
945
|
-
function isBlocksConfigShape(raw) {
|
|
946
|
-
if (!isPlainObject(raw)) return false;
|
|
947
|
-
if (raw.navigation !== void 0 && !Array.isArray(raw.navigation)) return false;
|
|
948
|
-
if (raw.linkCards !== void 0 && !Array.isArray(raw.linkCards)) return false;
|
|
949
|
-
return true;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
980
|
// src/core/host-commands.ts
|
|
953
981
|
function commandEventName(cmd) {
|
|
954
982
|
return `${BRAND.cssPrefix}:${cmd}`;
|
|
@@ -969,6 +997,155 @@ function bindHostCommands(host, handlers) {
|
|
|
969
997
|
};
|
|
970
998
|
}
|
|
971
999
|
|
|
1000
|
+
// src/core/ids.ts
|
|
1001
|
+
function uuid7() {
|
|
1002
|
+
const ts = Date.now();
|
|
1003
|
+
const bytes = new Uint8Array(16);
|
|
1004
|
+
bytes[0] = Math.floor(ts / 2 ** 40) & 255;
|
|
1005
|
+
bytes[1] = Math.floor(ts / 2 ** 32) & 255;
|
|
1006
|
+
bytes[2] = ts >>> 24 & 255;
|
|
1007
|
+
bytes[3] = ts >>> 16 & 255;
|
|
1008
|
+
bytes[4] = ts >>> 8 & 255;
|
|
1009
|
+
bytes[5] = ts & 255;
|
|
1010
|
+
fillRandom(bytes.subarray(6));
|
|
1011
|
+
bytes[6] = bytes[6] & 15 | 112;
|
|
1012
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1013
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1014
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1015
|
+
}
|
|
1016
|
+
function fillRandom(view) {
|
|
1017
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
1018
|
+
crypto.getRandomValues(view);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
for (let i = 0; i < view.length; i++) view[i] = Math.floor(Math.random() * 256);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// src/core/persistence.ts
|
|
1025
|
+
var log2 = logger.scope("persistence");
|
|
1026
|
+
function createPersistence(widgetId, storage = defaultStorage) {
|
|
1027
|
+
const prefix = `${BRAND.cssPrefix}.${widgetId}`;
|
|
1028
|
+
const KEY_VISITOR = `${prefix}.visitorId`;
|
|
1029
|
+
const KEY_CONVERSATION = `${prefix}.conversationId`;
|
|
1030
|
+
const KEY_USER_PREFS = `${prefix}.userPrefs`;
|
|
1031
|
+
const KEY_PANEL_OPEN = `${prefix}.panelOpen`;
|
|
1032
|
+
const KEY_PANEL_SIZE = `${prefix}.panelSize`;
|
|
1033
|
+
const KEY_CALLOUT_DISMISSED = `${prefix}.calloutDismissed`;
|
|
1034
|
+
const KEY_SIDEBAR_COLLAPSED = `${prefix}.sidebarCollapsed`;
|
|
1035
|
+
const KEY_ACTIVE_MODULE = `${prefix}.activeModule`;
|
|
1036
|
+
const KEY_FORMS_DONE = `${prefix}.formsDone`;
|
|
1037
|
+
const persistence = {
|
|
1038
|
+
getVisitorId() {
|
|
1039
|
+
const existing = storage.get(KEY_VISITOR);
|
|
1040
|
+
if (existing) return existing;
|
|
1041
|
+
const minted = uuid7();
|
|
1042
|
+
storage.set(KEY_VISITOR, minted);
|
|
1043
|
+
return minted;
|
|
1044
|
+
},
|
|
1045
|
+
setVisitorId(id) {
|
|
1046
|
+
log2.debug("setVisitorId (rebind)", { id, widgetId });
|
|
1047
|
+
storage.set(KEY_VISITOR, id);
|
|
1048
|
+
},
|
|
1049
|
+
loadConversationId() {
|
|
1050
|
+
return storage.get(KEY_CONVERSATION) ?? void 0;
|
|
1051
|
+
},
|
|
1052
|
+
saveConversationId(id) {
|
|
1053
|
+
log2.trace("saveConversationId", { id, widgetId });
|
|
1054
|
+
if (id) storage.set(KEY_CONVERSATION, id);
|
|
1055
|
+
else storage.remove(KEY_CONVERSATION);
|
|
1056
|
+
},
|
|
1057
|
+
loadUserPrefs() {
|
|
1058
|
+
const raw = storage.get(KEY_USER_PREFS);
|
|
1059
|
+
if (!raw) return {};
|
|
1060
|
+
try {
|
|
1061
|
+
const parsed = JSON.parse(raw);
|
|
1062
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
log2.warn("loadUserPrefs parse failed", { error });
|
|
1065
|
+
return {};
|
|
1066
|
+
}
|
|
1067
|
+
},
|
|
1068
|
+
saveUserPrefs(s) {
|
|
1069
|
+
try {
|
|
1070
|
+
storage.set(KEY_USER_PREFS, JSON.stringify(s));
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
log2.warn("saveUserPrefs serialise failed", { error });
|
|
1073
|
+
}
|
|
1074
|
+
},
|
|
1075
|
+
patchUserPrefs(patch) {
|
|
1076
|
+
const current = persistence.loadUserPrefs();
|
|
1077
|
+
persistence.saveUserPrefs({ ...current, ...patch });
|
|
1078
|
+
},
|
|
1079
|
+
loadPanelOpen() {
|
|
1080
|
+
const raw = storage.get(KEY_PANEL_OPEN);
|
|
1081
|
+
if (raw === "1") return true;
|
|
1082
|
+
if (raw === "0") return false;
|
|
1083
|
+
return null;
|
|
1084
|
+
},
|
|
1085
|
+
savePanelOpen(open) {
|
|
1086
|
+
storage.set(KEY_PANEL_OPEN, open ? "1" : "0");
|
|
1087
|
+
},
|
|
1088
|
+
loadPanelSize() {
|
|
1089
|
+
const raw = storage.get(KEY_PANEL_SIZE);
|
|
1090
|
+
return raw === "normal" || raw === "expanded" || raw === "fullscreen" ? raw : null;
|
|
1091
|
+
},
|
|
1092
|
+
savePanelSize(size) {
|
|
1093
|
+
storage.set(KEY_PANEL_SIZE, size);
|
|
1094
|
+
},
|
|
1095
|
+
loadCalloutDismissed(currentText) {
|
|
1096
|
+
if (!currentText) return false;
|
|
1097
|
+
const dismissedText = storage.get(KEY_CALLOUT_DISMISSED);
|
|
1098
|
+
return !!dismissedText && dismissedText === currentText;
|
|
1099
|
+
},
|
|
1100
|
+
saveCalloutDismissed(currentText) {
|
|
1101
|
+
if (currentText) storage.set(KEY_CALLOUT_DISMISSED, currentText);
|
|
1102
|
+
else storage.remove(KEY_CALLOUT_DISMISSED);
|
|
1103
|
+
},
|
|
1104
|
+
loadSidebarCollapsed() {
|
|
1105
|
+
const raw = storage.get(KEY_SIDEBAR_COLLAPSED);
|
|
1106
|
+
if (raw === "1") return true;
|
|
1107
|
+
if (raw === "0") return false;
|
|
1108
|
+
return null;
|
|
1109
|
+
},
|
|
1110
|
+
saveSidebarCollapsed(collapsed) {
|
|
1111
|
+
storage.set(KEY_SIDEBAR_COLLAPSED, collapsed ? "1" : "0");
|
|
1112
|
+
},
|
|
1113
|
+
loadActiveModule() {
|
|
1114
|
+
return storage.get(KEY_ACTIVE_MODULE) || null;
|
|
1115
|
+
},
|
|
1116
|
+
saveActiveModule(id) {
|
|
1117
|
+
storage.set(KEY_ACTIVE_MODULE, id);
|
|
1118
|
+
},
|
|
1119
|
+
loadFormsDone() {
|
|
1120
|
+
const raw = storage.get(KEY_FORMS_DONE);
|
|
1121
|
+
if (!raw) return {};
|
|
1122
|
+
try {
|
|
1123
|
+
const parsed = JSON.parse(raw);
|
|
1124
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
log2.warn("loadFormsDone parse failed", { error });
|
|
1127
|
+
return {};
|
|
1128
|
+
}
|
|
1129
|
+
},
|
|
1130
|
+
saveFormDone(id) {
|
|
1131
|
+
try {
|
|
1132
|
+
const done = persistence.loadFormsDone();
|
|
1133
|
+
done[id] = Date.now();
|
|
1134
|
+
storage.set(KEY_FORMS_DONE, JSON.stringify(done));
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
log2.warn("saveFormDone serialise failed", { error });
|
|
1137
|
+
}
|
|
1138
|
+
},
|
|
1139
|
+
clearConversation() {
|
|
1140
|
+
storage.remove(KEY_CONVERSATION);
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
return persistence;
|
|
1144
|
+
}
|
|
1145
|
+
function isPlainObject(v) {
|
|
1146
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
972
1149
|
// src/styles/tokens.css
|
|
973
1150
|
var tokens_default = ':host{--__P__-accent-user: __BRAND_ACCENT__;--__P__-accent: var(--__P__-accent-user);--__P__-accent-text-user: __BRAND_ACCENT_TEXT__;--__P__-accent-text: var(--__P__-accent-text-user);--__P__-radius: __BRAND_RADIUS__;--__P__-radius-sm: 8px;--__P__-radius-md: 10px;--__P__-radius-lg: 16px;--__P__-font: __BRAND_FONT__;--__P__-font-display: var(--__P__-font);--__P__-font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;--__P__-bg: oklch(1 0 0);--__P__-bg-elevated: oklch(.9789 .0029 278.3);--__P__-surface: var(--__P__-bg-elevated);--__P__-muted: var(--__P__-fg-muted);--__P__-hover: oklch(.1686 .011 278.3 / .06);--__P__-fg: oklch(.1686 .011 278.3);--__P__-fg-muted: oklch(.4932 .0301 278.3);--__P__-border: oklch(.9312 .0084 278.3);--__P__-border-strong: oklch(.8757 .0131 278.3);--__P__-bubble-user: var(--__P__-accent);--__P__-bubble-user-text: var(--__P__-accent-text);--__P__-bubble-assistant: oklch(.9638 .0058 278.3);--__P__-bubble-assistant-text: var(--__P__-fg);--__P__-on-accent: var(--__P__-accent-text);--__P__-success: oklch(.6456 .1654 151.53);--__P__-warning: oklch(.7135 .1494 74.05);--__P__-danger: oklch(.5966 .1818 24.51);--__P__-danger-text: oklch(.5054 .1905 27.52);--__P__-danger-bg: oklch(.5966 .1818 24.51 / .1);--__P__-neutral: oklch(.7137 .0192 278.3);--__P__-focus: var(--__P__-accent);--__P__-shadow-panel: 0 8px 24px -8px rgba(13, 15, 20, .18), 0 24px 64px -24px rgba(13, 15, 20, .24);--__P__-shadow-card: 0 1px 2px rgba(13, 15, 20, .06), 0 4px 12px -6px rgba(13, 15, 20, .12);--__P__-backdrop: oklch(.1686 .011 278.3 / .55);--__P__-shadow-fab: 0 6px 16px -4px color-mix(in oklch, var(--__P__-accent) 45%, transparent), 0 12px 32px -12px rgba(13, 15, 20, .25);--__P__-z-fab: 2147483600;--__P__-z-panel: 2147483601;--__P__-panel-w: 380px;--__P__-panel-h: 600px;--__P__-fab-size: 56px;--__P__-space-1: 4px;--__P__-space-2: 8px;--__P__-space-3: 12px;--__P__-space-4: 16px;--__P__-space-5: 20px;--__P__-space-6: 24px;--__P__-space-8: 32px;--__P__-text-scale: 1;--__P__-text-xs: calc(12px * var(--__P__-text-scale));--__P__-text-sm: calc(13px * var(--__P__-text-scale));--__P__-text-base: calc(15px * var(--__P__-text-scale));--__P__-text-md: calc(16px * var(--__P__-text-scale));--__P__-text-lg: calc(18px * var(--__P__-text-scale));--__P__-text-xl: calc(22px * var(--__P__-text-scale));--__P__-text-2xl: calc(30px * var(--__P__-text-scale))}:host([data-text-size="small"]){--__P__-text-scale: .9}:host([data-text-size="large"]){--__P__-text-scale: 1.15}:host{--__P__-ease: cubic-bezier(.2, .7, .2, 1);--__P__-ease-out: cubic-bezier(.2, .7, .2, 1);--__P__-ease-in: cubic-bezier(.4, 0, 1, 1);--__P__-ease-in-out: cubic-bezier(.65, 0, .35, 1);--__P__-dur-quick: .12s;--__P__-dur-base: .22s;--__P__-dur-slow: .32s;font-family:var(--__P__-font);color:var(--__P__-fg);color-scheme:light;--__P__-color-scheme: light}:host([data-theme="dark"]){--__P__-bg: oklch(.1783 .0128 278.3);--__P__-bg-elevated: oklch(.2232 .0182 278.3);--__P__-fg: oklch(.9368 .01 278.3);--__P__-fg-muted: oklch(.7048 .0296 278.3);--__P__-border: oklch(.2752 .0266 278.3);--__P__-border-strong: oklch(.335 .0291 278.3);--__P__-hover: oklch(.9368 .01 278.3 / .08);--__P__-bubble-assistant: oklch(.2474 .0311 278.3);--__P__-bubble-assistant-text: var(--__P__-fg);--__P__-danger-text: oklch(.8077 .1035 19.57);--__P__-danger-bg: oklch(.5966 .1818 24.51 / .18);--__P__-shadow-panel: 0 8px 24px -8px rgba(0, 0, 0, .6), 0 24px 64px -24px rgba(0, 0, 0, .7);--__P__-shadow-card: 0 1px 2px rgba(0, 0, 0, .4), 0 4px 12px -6px rgba(0, 0, 0, .5);--__P__-backdrop: rgba(0, 0, 0, .65);color-scheme:dark;--__P__-color-scheme: dark}@media(prefers-color-scheme:dark){:host([data-theme="auto"]){--__P__-bg: oklch(.1783 .0128 278.3);--__P__-bg-elevated: oklch(.2232 .0182 278.3);--__P__-fg: oklch(.9368 .01 278.3);--__P__-fg-muted: oklch(.7048 .0296 278.3);--__P__-border: oklch(.2752 .0266 278.3);--__P__-border-strong: oklch(.335 .0291 278.3);--__P__-hover: oklch(.9368 .01 278.3 / .08);--__P__-bubble-assistant: oklch(.2474 .0311 278.3);--__P__-bubble-assistant-text: var(--__P__-fg);--__P__-shadow-panel: 0 8px 24px -8px rgba(0, 0, 0, .6), 0 24px 64px -24px rgba(0, 0, 0, .7);color-scheme:dark;--__P__-color-scheme: dark}}@supports (color: oklch(from red l c h)){:host{--__P__-accent: oklch(from var(--__P__-accent-user) clamp(.3, l, .62) c h);--__P__-accent-text: oklch(from var(--__P__-accent) clamp(0, (l / .623 - 1) * -infinity, 1) 0 h)}:host([data-theme="dark"]){--__P__-accent: oklch(from var(--__P__-accent-user) clamp(.65, l, .85) c h)}@media(prefers-color-scheme:dark){:host([data-theme="auto"]){--__P__-accent: oklch(from var(--__P__-accent-user) clamp(.65, l, .85) c h)}}}@media(prefers-reduced-motion:reduce){:host{--__P__-dur-quick: 1ms;--__P__-dur-base: 1ms;--__P__-dur-slow: 1ms}}\n';
|
|
974
1151
|
|
|
@@ -993,7 +1170,7 @@ var FONT = true ? "Inter, system-ui, -apple-system, 'Segoe UI', sans-serif" : "I
|
|
|
993
1170
|
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);
|
|
994
1171
|
|
|
995
1172
|
// src/shadow/mount.ts
|
|
996
|
-
var
|
|
1173
|
+
var log3 = logger.scope("mount");
|
|
997
1174
|
var sheetsByDocument = /* @__PURE__ */ new WeakMap();
|
|
998
1175
|
var HOST_CSS = {
|
|
999
1176
|
floating: "all: initial; position: fixed; z-index: 2147483600;",
|
|
@@ -1010,7 +1187,7 @@ function hostPositionForMode(mode) {
|
|
|
1010
1187
|
}
|
|
1011
1188
|
function createShadowMount(opts = {}) {
|
|
1012
1189
|
const position = opts.position ?? "flow";
|
|
1013
|
-
|
|
1190
|
+
log3.debug("createShadowMount", { position, hasTarget: !!opts.target, hasHost: !!opts.existingHost });
|
|
1014
1191
|
const tagName = `${BRAND.cssPrefix}-chat-host`;
|
|
1015
1192
|
if (typeof customElements !== "undefined" && !customElements.get(tagName)) {
|
|
1016
1193
|
customElements.define(tagName, class extends HTMLElement {
|
|
@@ -1094,6 +1271,123 @@ function applyHostAttributes(host, resolved) {
|
|
|
1094
1271
|
applySize(host, resolved.size);
|
|
1095
1272
|
}
|
|
1096
1273
|
|
|
1274
|
+
// src/shadow/iframe-mount.ts
|
|
1275
|
+
var log4 = logger.scope("iframe");
|
|
1276
|
+
var IFRAME_SRCDOC = '<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;padding:0;background:transparent;overflow:hidden}</style></head><body></body></html>';
|
|
1277
|
+
function createIframeMount(opts, position, initialMode = "closed", layoutMode = "floating", target = null) {
|
|
1278
|
+
log4.debug("createIframeMount", { position, initialMode, layoutMode, hasTarget: !!target });
|
|
1279
|
+
const iframe = document.createElement("iframe");
|
|
1280
|
+
iframe.title = `${BRAND.name} chat`;
|
|
1281
|
+
iframe.setAttribute("allow", "microphone; clipboard-write");
|
|
1282
|
+
sizeIframe(iframe, position, initialMode, layoutMode);
|
|
1283
|
+
const parent = layoutMode === "inline" && target ? target : document.body;
|
|
1284
|
+
parent.appendChild(iframe);
|
|
1285
|
+
const doc = iframe.contentDocument;
|
|
1286
|
+
if (!doc) throw new Error("iframe contentDocument unavailable (cross-origin?)");
|
|
1287
|
+
doc.open();
|
|
1288
|
+
doc.write(IFRAME_SRCDOC);
|
|
1289
|
+
doc.close();
|
|
1290
|
+
const mount2 = createShadowMount({ ...opts, target: doc.body });
|
|
1291
|
+
const mo = new MutationObserver(() => {
|
|
1292
|
+
sizeIframe(iframe, position, mount2.hostElement.dataset.mode ?? "closed", layoutMode);
|
|
1293
|
+
});
|
|
1294
|
+
mo.observe(mount2.hostElement, { attributes: true, attributeFilter: ["data-mode"] });
|
|
1295
|
+
return {
|
|
1296
|
+
...mount2,
|
|
1297
|
+
iframeElement: iframe,
|
|
1298
|
+
destroy() {
|
|
1299
|
+
mo.disconnect();
|
|
1300
|
+
mount2.destroy();
|
|
1301
|
+
iframe.remove();
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
var BASE_CSS = "border:0;background:transparent;z-index:2147483647;pointer-events:auto;";
|
|
1306
|
+
var FLOATING_SIZE_BY_MODE = {
|
|
1307
|
+
closed: { w: "88px", h: "88px" },
|
|
1308
|
+
expanded: { w: "min(calc(100vw - 32px), 680px)", h: "min(calc(100vh - 32px), 860px)" },
|
|
1309
|
+
// `open` (normal) — also the fallback for any other non-fullscreen state.
|
|
1310
|
+
open: { w: "min(calc(100vw - 32px), 440px)", h: "min(calc(100vh - 32px), 700px)" }
|
|
1311
|
+
};
|
|
1312
|
+
function sizeIframe(iframe, position, mode, layoutMode) {
|
|
1313
|
+
if (mode === "fullscreen" || layoutMode === "standalone") {
|
|
1314
|
+
iframe.style.cssText = `${BASE_CSS}position:fixed;inset:0;width:100vw;height:100vh;`;
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
if (layoutMode === "inline") {
|
|
1318
|
+
iframe.style.cssText = `${BASE_CSS}position:static;display:block;width:100%;height:100%;`;
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
const compact = typeof matchMedia === "function" && matchMedia("(max-width: 640px)").matches;
|
|
1322
|
+
if (compact && (mode === "open" || mode === "expanded")) {
|
|
1323
|
+
iframe.style.cssText = `${BASE_CSS}position:fixed;inset:0;width:100vw;height:100vh;`;
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
const vKey = position.startsWith("top-") ? "top" : "bottom";
|
|
1327
|
+
const hKey = position.endsWith("-left") ? "left" : "right";
|
|
1328
|
+
const { w, h: h2 } = FLOATING_SIZE_BY_MODE[mode] ?? FLOATING_SIZE_BY_MODE.open;
|
|
1329
|
+
iframe.style.cssText = `${BASE_CSS}position:fixed;${vKey}:16px;${hKey}:16px;width:${w};height:${h2};`;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// src/core/events.ts
|
|
1333
|
+
var EventBus = class {
|
|
1334
|
+
constructor() {
|
|
1335
|
+
__publicField(this, "handlers", /* @__PURE__ */ new Map());
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Register a listener for `name`.
|
|
1339
|
+
* @returns an `off` function that removes this listener.
|
|
1340
|
+
*/
|
|
1341
|
+
on(name, fn) {
|
|
1342
|
+
let set = this.handlers.get(name);
|
|
1343
|
+
if (!set) {
|
|
1344
|
+
set = /* @__PURE__ */ new Set();
|
|
1345
|
+
this.handlers.set(name, set);
|
|
1346
|
+
}
|
|
1347
|
+
set.add(fn);
|
|
1348
|
+
return () => this.handlers.get(name)?.delete(fn);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Dispatch an event synchronously to all listeners.
|
|
1352
|
+
* Listener errors are swallowed — the widget must not crash on user callbacks.
|
|
1353
|
+
*/
|
|
1354
|
+
emit(name, payload) {
|
|
1355
|
+
const set = this.handlers.get(name);
|
|
1356
|
+
if (!set) return;
|
|
1357
|
+
for (const fn of set) {
|
|
1358
|
+
try {
|
|
1359
|
+
fn(payload);
|
|
1360
|
+
} catch {
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
/** Remove every listener. Called by the instance `destroy()`. */
|
|
1365
|
+
clear() {
|
|
1366
|
+
this.handlers.clear();
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
// src/core/boot.ts
|
|
1371
|
+
import { h, render as renderPreact } from "preact";
|
|
1372
|
+
|
|
1373
|
+
// src/ui/app.tsx
|
|
1374
|
+
import { useCallback as useCallback6, useEffect as useEffect17, useMemo as useMemo3, useRef as useRef9, useState as useState13 } from "preact/hooks";
|
|
1375
|
+
import { useComputed as useComputed7, useSignal as useSignal2 } from "@preact/signals";
|
|
1376
|
+
|
|
1377
|
+
// src/core/handshake-shape.ts
|
|
1378
|
+
function isPlainObject2(raw) {
|
|
1379
|
+
return !!raw && typeof raw === "object" && !Array.isArray(raw);
|
|
1380
|
+
}
|
|
1381
|
+
function isSiteConfigShape(raw) {
|
|
1382
|
+
return isPlainObject2(raw);
|
|
1383
|
+
}
|
|
1384
|
+
function isBlocksConfigShape(raw) {
|
|
1385
|
+
if (!isPlainObject2(raw)) return false;
|
|
1386
|
+
if (raw.navigation !== void 0 && !Array.isArray(raw.navigation)) return false;
|
|
1387
|
+
if (raw.linkCards !== void 0 && !Array.isArray(raw.linkCards)) return false;
|
|
1388
|
+
return true;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1097
1391
|
// src/ui/host-mode.ts
|
|
1098
1392
|
function computeHostMode(configured, panelSize, isOpen) {
|
|
1099
1393
|
if (configured === "page") return "page";
|
|
@@ -1144,30 +1438,6 @@ function createAuth(opts) {
|
|
|
1144
1438
|
};
|
|
1145
1439
|
}
|
|
1146
1440
|
|
|
1147
|
-
// src/core/ids.ts
|
|
1148
|
-
function uuid7() {
|
|
1149
|
-
const ts = Date.now();
|
|
1150
|
-
const bytes = new Uint8Array(16);
|
|
1151
|
-
bytes[0] = Math.floor(ts / 2 ** 40) & 255;
|
|
1152
|
-
bytes[1] = Math.floor(ts / 2 ** 32) & 255;
|
|
1153
|
-
bytes[2] = ts >>> 24 & 255;
|
|
1154
|
-
bytes[3] = ts >>> 16 & 255;
|
|
1155
|
-
bytes[4] = ts >>> 8 & 255;
|
|
1156
|
-
bytes[5] = ts & 255;
|
|
1157
|
-
fillRandom(bytes.subarray(6));
|
|
1158
|
-
bytes[6] = bytes[6] & 15 | 112;
|
|
1159
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
1160
|
-
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1161
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1162
|
-
}
|
|
1163
|
-
function fillRandom(view) {
|
|
1164
|
-
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
1165
|
-
crypto.getRandomValues(view);
|
|
1166
|
-
return;
|
|
1167
|
-
}
|
|
1168
|
-
for (let i = 0; i < view.length; i++) view[i] = Math.floor(Math.random() * 256);
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
1441
|
// src/stream/types.ts
|
|
1172
1442
|
var StreamError = class extends Error {
|
|
1173
1443
|
constructor(message, code, status, cause) {
|
|
@@ -1180,7 +1450,7 @@ var StreamError = class extends Error {
|
|
|
1180
1450
|
};
|
|
1181
1451
|
|
|
1182
1452
|
// src/stream/parser.ts
|
|
1183
|
-
var
|
|
1453
|
+
var log5 = logger.scope("parser");
|
|
1184
1454
|
async function* parseChatStream(response, signal7) {
|
|
1185
1455
|
if (!response.ok) {
|
|
1186
1456
|
const text = await response.text().catch(() => "");
|
|
@@ -1232,10 +1502,10 @@ ${part}` : part;
|
|
|
1232
1502
|
if (!dataPayload || dataPayload === "[DONE]") return null;
|
|
1233
1503
|
try {
|
|
1234
1504
|
const chunk = JSON.parse(dataPayload);
|
|
1235
|
-
|
|
1505
|
+
log5.trace("chunk", { type: chunk.type, eventId });
|
|
1236
1506
|
return { chunk, eventId };
|
|
1237
1507
|
} catch (err) {
|
|
1238
|
-
|
|
1508
|
+
log5.trace("frame parse failed", { err, payload: dataPayload.slice(0, 200) });
|
|
1239
1509
|
return null;
|
|
1240
1510
|
}
|
|
1241
1511
|
}
|
|
@@ -1390,7 +1660,7 @@ function messageToWireParts(m) {
|
|
|
1390
1660
|
}
|
|
1391
1661
|
|
|
1392
1662
|
// src/stream/transport.ts
|
|
1393
|
-
var
|
|
1663
|
+
var log6 = logger.scope("transport");
|
|
1394
1664
|
var MAX_RESUME_ATTEMPTS = 3;
|
|
1395
1665
|
var RESUME_BACKOFF_MS = 400;
|
|
1396
1666
|
var CONTENT_CACHE_TTL_MS = 6e4;
|
|
@@ -1516,13 +1786,13 @@ var AgentTransport = class {
|
|
|
1516
1786
|
}
|
|
1517
1787
|
/** One-shot runtime bootstrap. Called once after the widget mounts. */
|
|
1518
1788
|
async handshake(body) {
|
|
1519
|
-
|
|
1789
|
+
log6.debug("handshake \u2192", {
|
|
1520
1790
|
visitorId: body.visitorId,
|
|
1521
1791
|
conversationId: body.conversationId,
|
|
1522
1792
|
locale: body.locale
|
|
1523
1793
|
});
|
|
1524
1794
|
const res = await this.postJson(DEFAULT_PATHS.handshake, body, "handshake");
|
|
1525
|
-
|
|
1795
|
+
log6.debug("handshake \u2190", {
|
|
1526
1796
|
visitorId: res.visitorId,
|
|
1527
1797
|
conversationId: res.conversationId,
|
|
1528
1798
|
hasWelcome: !!res.welcome,
|
|
@@ -1530,7 +1800,7 @@ var AgentTransport = class {
|
|
|
1530
1800
|
rebind: res.rebind
|
|
1531
1801
|
});
|
|
1532
1802
|
if (!isHandshakeResponseShape(res)) {
|
|
1533
|
-
|
|
1803
|
+
log6.warn("handshake response did not match expected shape; using raw response", { response: res });
|
|
1534
1804
|
}
|
|
1535
1805
|
this.visitorId = res.visitorId ?? body.visitorId;
|
|
1536
1806
|
this.conversationId = res.conversationId;
|
|
@@ -1538,11 +1808,11 @@ var AgentTransport = class {
|
|
|
1538
1808
|
}
|
|
1539
1809
|
/** Upload a file attachment. Returns the `{id, url}` the backend assigns. */
|
|
1540
1810
|
async uploadFile(file) {
|
|
1541
|
-
|
|
1811
|
+
log6.debug("uploadFile \u2192", { name: file.name, size: file.size, type: file.type });
|
|
1542
1812
|
const fd = new FormData();
|
|
1543
1813
|
fd.append("file", file, file.name);
|
|
1544
1814
|
const res = await this.postForm(this.uploadPath, fd, "upload");
|
|
1545
|
-
|
|
1815
|
+
log6.debug("uploadFile \u2190", { id: res.id, url: res.url });
|
|
1546
1816
|
return res;
|
|
1547
1817
|
}
|
|
1548
1818
|
/**
|
|
@@ -1550,12 +1820,12 @@ var AgentTransport = class {
|
|
|
1550
1820
|
* Only used when `features.voice === 'server'`.
|
|
1551
1821
|
*/
|
|
1552
1822
|
async transcribe(audio, mimeType = "audio/webm") {
|
|
1553
|
-
|
|
1823
|
+
log6.debug("transcribe \u2192", { size: audio.size, mimeType });
|
|
1554
1824
|
const fd = new FormData();
|
|
1555
1825
|
fd.append("audio", audio, `voice.${extensionFor(mimeType)}`);
|
|
1556
1826
|
fd.append("mimeType", mimeType);
|
|
1557
1827
|
const res = await this.postForm(this.transcribePath, fd, "transcribe");
|
|
1558
|
-
|
|
1828
|
+
log6.debug("transcribe \u2190", { textLen: res.text.length });
|
|
1559
1829
|
return res;
|
|
1560
1830
|
}
|
|
1561
1831
|
async listConversations(params = {}) {
|
|
@@ -1563,14 +1833,14 @@ var AgentTransport = class {
|
|
|
1563
1833
|
if (params.visitorId) url.searchParams.set("visitorId", params.visitorId);
|
|
1564
1834
|
if (params.limit) url.searchParams.set("limit", String(params.limit));
|
|
1565
1835
|
if (params.cursor) url.searchParams.set("cursor", params.cursor);
|
|
1566
|
-
|
|
1836
|
+
log6.debug("listConversations \u2192", {
|
|
1567
1837
|
visitorId: params.visitorId ?? this.visitorId,
|
|
1568
1838
|
limit: params.limit,
|
|
1569
1839
|
cursor: params.cursor
|
|
1570
1840
|
});
|
|
1571
1841
|
const res = await this.getJson(url.toString(), "listConversations");
|
|
1572
1842
|
const conversations = res.conversations ?? [];
|
|
1573
|
-
|
|
1843
|
+
log6.debug("listConversations \u2190", { count: conversations.length, nextCursor: res.nextCursor });
|
|
1574
1844
|
return {
|
|
1575
1845
|
conversations: conversations.map((conversation) => ({
|
|
1576
1846
|
conversationId: conversation.conversationId,
|
|
@@ -1586,10 +1856,10 @@ var AgentTransport = class {
|
|
|
1586
1856
|
async loadConversation(conversationId) {
|
|
1587
1857
|
const url = new URL(this.url(DEFAULT_PATHS.listMessages));
|
|
1588
1858
|
url.searchParams.set("conversationId", conversationId);
|
|
1589
|
-
|
|
1859
|
+
log6.debug("loadConversation \u2192", { conversationId, visitorId: this.visitorId });
|
|
1590
1860
|
const res = await this.getJson(url.toString(), "loadConversation");
|
|
1591
1861
|
const messages = res.messages ?? [];
|
|
1592
|
-
|
|
1862
|
+
log6.debug("loadConversation \u2190", {
|
|
1593
1863
|
conversationId: res.conversationId,
|
|
1594
1864
|
canContinue: res.canContinue,
|
|
1595
1865
|
messageCount: messages.length,
|
|
@@ -1611,19 +1881,19 @@ var AgentTransport = class {
|
|
|
1611
1881
|
*/
|
|
1612
1882
|
async markRead(conversationId) {
|
|
1613
1883
|
if (!this.visitorId || !conversationId) return;
|
|
1614
|
-
|
|
1884
|
+
log6.debug("markRead \u2192", { conversationId, visitorId: this.visitorId });
|
|
1615
1885
|
try {
|
|
1616
1886
|
await this.postJson(DEFAULT_PATHS.markRead, { conversationId }, "markRead");
|
|
1617
1887
|
} catch (err) {
|
|
1618
|
-
|
|
1888
|
+
log6.debug("markRead failed (non-fatal)", { err });
|
|
1619
1889
|
}
|
|
1620
1890
|
}
|
|
1621
1891
|
/** Fetch content rows by filter. `GET /pai/content` (data-API), TTL-cached. */
|
|
1622
1892
|
listContent(query = {}) {
|
|
1623
|
-
const key = JSON.stringify(query, Object.keys(query).toSorted())
|
|
1893
|
+
const key = `${this.locale ?? ""}|${JSON.stringify(query, Object.keys(query).toSorted())}`;
|
|
1624
1894
|
const cached = this.contentCache.get(key);
|
|
1625
1895
|
if (cached && Date.now() - cached.at < CONTENT_CACHE_TTL_MS) {
|
|
1626
|
-
|
|
1896
|
+
log6.debug("listContent \u2192 cache", query);
|
|
1627
1897
|
return cached.result;
|
|
1628
1898
|
}
|
|
1629
1899
|
const result = this.fetchContent(query).catch((err) => {
|
|
@@ -1639,10 +1909,10 @@ var AgentTransport = class {
|
|
|
1639
1909
|
if (value === void 0) continue;
|
|
1640
1910
|
url.searchParams.set(key, Array.isArray(value) ? value.join(",") : String(value));
|
|
1641
1911
|
}
|
|
1642
|
-
|
|
1912
|
+
log6.debug("listContent \u2192", query);
|
|
1643
1913
|
const res = await this.getJson(url.toString(), "listContent");
|
|
1644
|
-
const items = res.items ?? [];
|
|
1645
|
-
|
|
1914
|
+
const items = (res.items ?? []).map((item) => overlayContent(item, this.locale));
|
|
1915
|
+
log6.debug("listContent \u2190", { count: items.length, total: res.pagination?.total });
|
|
1646
1916
|
return { ...res, items };
|
|
1647
1917
|
}
|
|
1648
1918
|
/**
|
|
@@ -1660,21 +1930,21 @@ var AgentTransport = class {
|
|
|
1660
1930
|
async bootstrap(conversationId) {
|
|
1661
1931
|
const url = new URL(this.dataUrl(DEFAULT_PATHS.bootstrap));
|
|
1662
1932
|
if (conversationId) url.searchParams.set("conversationId", conversationId);
|
|
1663
|
-
|
|
1933
|
+
log6.debug("bootstrap \u2192", { conversationId: conversationId ?? this.conversationId });
|
|
1664
1934
|
const res = await this.getJson(url.toString(), "bootstrap");
|
|
1665
1935
|
const forms = res.forms ?? [];
|
|
1666
1936
|
const formSubmissions = res.formSubmissions ?? [];
|
|
1667
|
-
|
|
1937
|
+
log6.debug("bootstrap \u2190", { forms: forms.length, formSubmissions: formSubmissions.length });
|
|
1668
1938
|
return { forms, formSubmissions };
|
|
1669
1939
|
}
|
|
1670
1940
|
async saveUserPrefs(userPrefs) {
|
|
1671
|
-
|
|
1941
|
+
log6.debug("saveUserPrefs \u2192", {
|
|
1672
1942
|
visitorId: this.visitorId,
|
|
1673
1943
|
conversationId: this.conversationId,
|
|
1674
1944
|
settingsKeys: Object.keys(userPrefs)
|
|
1675
1945
|
});
|
|
1676
1946
|
const res = await this.postJson(DEFAULT_PATHS.updateSettings, { userPrefs }, "saveUserPrefs");
|
|
1677
|
-
|
|
1947
|
+
log6.debug("saveUserPrefs \u2190", { ok: res.ok });
|
|
1678
1948
|
return res;
|
|
1679
1949
|
}
|
|
1680
1950
|
get uploadPath() {
|
|
@@ -1695,15 +1965,15 @@ var AgentTransport = class {
|
|
|
1695
1965
|
* that doesn't implement the endpoint) is non-fatal, so callers don't await.
|
|
1696
1966
|
*/
|
|
1697
1967
|
async submitForm(body) {
|
|
1698
|
-
|
|
1968
|
+
log6.debug("submitForm \u2192", { formId: body.formId, trigger: body.trigger, fields: Object.keys(body.values).length });
|
|
1699
1969
|
try {
|
|
1700
1970
|
await this.postJson(this.dataUrl(this.submitFormPath), body, "submitForm");
|
|
1701
1971
|
} catch (err) {
|
|
1702
|
-
|
|
1972
|
+
log6.debug("submitForm failed (non-fatal)", { err });
|
|
1703
1973
|
}
|
|
1704
1974
|
}
|
|
1705
1975
|
sendMessage(body) {
|
|
1706
|
-
|
|
1976
|
+
log6.debug("message \u2192", {
|
|
1707
1977
|
conversationId: body.conversationId,
|
|
1708
1978
|
visitorId: body.visitorId,
|
|
1709
1979
|
messageCount: body.messages.length,
|
|
@@ -1783,17 +2053,17 @@ var AgentTransport = class {
|
|
|
1783
2053
|
});
|
|
1784
2054
|
} catch (err) {
|
|
1785
2055
|
if (ctrl.signal.aborted) return;
|
|
1786
|
-
|
|
2056
|
+
log6.debug("resume fetch failed \u2014 retrying", { attempt, err });
|
|
1787
2057
|
continue;
|
|
1788
2058
|
}
|
|
1789
2059
|
if (res.status === 204 || !res.ok) {
|
|
1790
|
-
|
|
2060
|
+
log6.debug("resume \u2192 no in-flight stream", { status: res.status });
|
|
1791
2061
|
return;
|
|
1792
2062
|
}
|
|
1793
2063
|
const before = seenIds.size;
|
|
1794
2064
|
if (yield* this.drain(parseChatStream(res, ctrl.signal), seenIds, ctrl)) return;
|
|
1795
2065
|
if (seenIds.size === before) {
|
|
1796
|
-
|
|
2066
|
+
log6.debug("resume \u2192 stream closed with no new events; nothing to resume");
|
|
1797
2067
|
return;
|
|
1798
2068
|
}
|
|
1799
2069
|
}
|
|
@@ -1820,14 +2090,14 @@ var AgentTransport = class {
|
|
|
1820
2090
|
} catch (err) {
|
|
1821
2091
|
if (ctrl.signal.aborted) return true;
|
|
1822
2092
|
if (err instanceof StreamError && err.status !== void 0) throw err;
|
|
1823
|
-
|
|
2093
|
+
log6.debug("stream segment dropped", { err });
|
|
1824
2094
|
}
|
|
1825
2095
|
return false;
|
|
1826
2096
|
}
|
|
1827
2097
|
/** Abort + fire-and-forget POST to `/pai/cancel-stream`. Idempotent. */
|
|
1828
2098
|
cancelStream(ctrl, conversationId) {
|
|
1829
2099
|
if (ctrl.signal.aborted) return;
|
|
1830
|
-
|
|
2100
|
+
log6.debug("cancel stream", { conversationId, visitorId: this.visitorId });
|
|
1831
2101
|
ctrl.abort();
|
|
1832
2102
|
if (!this.visitorId || !conversationId) return;
|
|
1833
2103
|
this.fetchImpl(this.url(DEFAULT_PATHS.cancelStream), {
|
|
@@ -1836,7 +2106,7 @@ var AgentTransport = class {
|
|
|
1836
2106
|
headers: { "content-type": "application/json", ...this.opts.auth.headers() },
|
|
1837
2107
|
body: JSON.stringify(this.withEnvelope({})),
|
|
1838
2108
|
keepalive: true
|
|
1839
|
-
}).catch((err) =>
|
|
2109
|
+
}).catch((err) => log6.debug("cancel POST failed (non-fatal)", { err }));
|
|
1840
2110
|
}
|
|
1841
2111
|
// ---- Low-level fetch helpers ------------------------------------------
|
|
1842
2112
|
async *openMessageStream(body, signal7) {
|
|
@@ -1918,13 +2188,13 @@ var AgentTransport = class {
|
|
|
1918
2188
|
if (attempt >= MAX_REQUEST_RETRIES) {
|
|
1919
2189
|
throw new StreamError(`${label} failed: network error`, "network", void 0, err);
|
|
1920
2190
|
}
|
|
1921
|
-
|
|
2191
|
+
log6.debug("request network error \u2014 retrying", { label, attempt });
|
|
1922
2192
|
await sleep(backoffMs(attempt));
|
|
1923
2193
|
continue;
|
|
1924
2194
|
}
|
|
1925
2195
|
if (res.ok || !RETRYABLE_STATUS.has(res.status) || attempt >= MAX_REQUEST_RETRIES) return res;
|
|
1926
2196
|
const wait = retryAfterMs(res.headers) ?? backoffMs(attempt);
|
|
1927
|
-
|
|
2197
|
+
log6.debug("request retryable status \u2014 retrying", { label, status: res.status, attempt, wait });
|
|
1928
2198
|
await sleep(wait);
|
|
1929
2199
|
}
|
|
1930
2200
|
}
|
|
@@ -2119,7 +2389,7 @@ var TRIGGER = {
|
|
|
2119
2389
|
};
|
|
2120
2390
|
|
|
2121
2391
|
// src/stream/reducer.ts
|
|
2122
|
-
var
|
|
2392
|
+
var log7 = logger.scope("reducer");
|
|
2123
2393
|
var StreamReducer = class {
|
|
2124
2394
|
constructor(messagesSig) {
|
|
2125
2395
|
__publicField(this, "messagesSig", messagesSig);
|
|
@@ -2141,13 +2411,13 @@ var StreamReducer = class {
|
|
|
2141
2411
|
case "finish-step":
|
|
2142
2412
|
return;
|
|
2143
2413
|
case "finish":
|
|
2144
|
-
|
|
2414
|
+
log7.debug("finish", { reason: chunk.finishReason, canContinue: chunk.canContinue });
|
|
2145
2415
|
m.status = "complete";
|
|
2146
2416
|
if (chunk.finishReason) m.finishReason = chunk.finishReason;
|
|
2147
2417
|
this.bumpDone(m);
|
|
2148
2418
|
return;
|
|
2149
2419
|
case "error":
|
|
2150
|
-
|
|
2420
|
+
log7.warn("stream error chunk", { errorText: chunk.errorText });
|
|
2151
2421
|
m.status = "error";
|
|
2152
2422
|
m.errorText = chunk.errorText;
|
|
2153
2423
|
this.bumpDone(m);
|
|
@@ -2191,221 +2461,96 @@ var StreamReducer = class {
|
|
|
2191
2461
|
appendPart(m, { kind: "source", sourceId: chunk.sourceId, url: chunk.url, title: chunk.title });
|
|
2192
2462
|
return;
|
|
2193
2463
|
}
|
|
2194
|
-
case "data-notification":
|
|
2195
|
-
return;
|
|
2196
|
-
default: {
|
|
2197
|
-
const _exhaustive = chunk;
|
|
2198
|
-
void _exhaustive;
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
}
|
|
2202
|
-
/**
|
|
2203
|
-
* Terminal-state bump (`finish`/`error`). Touches the message's own `partsSig`
|
|
2204
|
-
* AND the list so subscribers re-render: a buffered bubble (subscribed to its
|
|
2205
|
-
* parts) flips out of the loading indicator into the finished reply, and the
|
|
2206
|
-
* list reflects the new `status`.
|
|
2207
|
-
*/
|
|
2208
|
-
bumpDone(m) {
|
|
2209
|
-
m.partsSig.value = [...m.partsSig.value];
|
|
2210
|
-
this.messagesSig.value = [...this.messagesSig.value];
|
|
2211
|
-
}
|
|
2212
|
-
};
|
|
2213
|
-
function ensureTextPart(m, kind, id) {
|
|
2214
|
-
const existing = m.partsSig.value.find((p33) => p33.kind === kind && p33.id === id);
|
|
2215
|
-
if (existing) return existing;
|
|
2216
|
-
const part = { kind, id, textSig: signal2(""), doneSig: signal2(false) };
|
|
2217
|
-
appendPart(m, part);
|
|
2218
|
-
return part;
|
|
2219
|
-
}
|
|
2220
|
-
function ensureToolPart(m, toolCallId, toolName) {
|
|
2221
|
-
const existing = m.partsSig.value.find((p33) => p33.kind === "tool" && p33.toolCallId === toolCallId);
|
|
2222
|
-
if (existing) return existing;
|
|
2223
|
-
const part = {
|
|
2224
|
-
kind: "tool",
|
|
2225
|
-
toolCallId,
|
|
2226
|
-
toolName: toolName ?? "tool",
|
|
2227
|
-
inputPartialSig: signal2(""),
|
|
2228
|
-
inputSig: signal2(void 0),
|
|
2229
|
-
outputSig: signal2(void 0),
|
|
2230
|
-
errorSig: signal2(void 0),
|
|
2231
|
-
stateSig: signal2("input"),
|
|
2232
|
-
approvalSig: signal2(void 0)
|
|
2233
|
-
};
|
|
2234
|
-
appendPart(m, part);
|
|
2235
|
-
return part;
|
|
2236
|
-
}
|
|
2237
|
-
function appendPart(m, part) {
|
|
2238
|
-
m.partsSig.value = [...m.partsSig.value, part];
|
|
2239
|
-
}
|
|
2240
|
-
function applyTool(m, chunk) {
|
|
2241
|
-
switch (chunk.type) {
|
|
2242
|
-
case "tool-input-start":
|
|
2243
|
-
ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2244
|
-
return;
|
|
2245
|
-
case "tool-input-delta": {
|
|
2246
|
-
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2247
|
-
p33.inputPartialSig.value += chunk.delta;
|
|
2248
|
-
return;
|
|
2249
|
-
}
|
|
2250
|
-
case "tool-input-available": {
|
|
2251
|
-
const p33 = ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2252
|
-
p33.inputSig.value = chunk.input;
|
|
2253
|
-
p33.stateSig.value = isAskUserInputTool(p33.toolName) ? "awaiting-input" : "awaiting";
|
|
2254
|
-
return;
|
|
2255
|
-
}
|
|
2256
|
-
case "tool-approval-request": {
|
|
2257
|
-
const p33 = ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2258
|
-
p33.approvalSig.value = { id: chunk.approvalId };
|
|
2259
|
-
p33.stateSig.value = "awaiting-approval";
|
|
2260
|
-
return;
|
|
2261
|
-
}
|
|
2262
|
-
case "tool-output-available": {
|
|
2263
|
-
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2264
|
-
p33.outputSig.value = chunk.output;
|
|
2265
|
-
p33.stateSig.value = "output";
|
|
2266
|
-
return;
|
|
2267
|
-
}
|
|
2268
|
-
case "tool-output-error": {
|
|
2269
|
-
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2270
|
-
p33.errorSig.value = chunk.errorText;
|
|
2271
|
-
p33.stateSig.value = "error";
|
|
2272
|
-
return;
|
|
2273
|
-
}
|
|
2274
|
-
default:
|
|
2275
|
-
return;
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
// src/core/persistence.ts
|
|
2280
|
-
var log6 = logger.scope("persistence");
|
|
2281
|
-
function createPersistence(widgetId, storage = defaultStorage) {
|
|
2282
|
-
const prefix = `${BRAND.cssPrefix}.${widgetId}`;
|
|
2283
|
-
const KEY_VISITOR = `${prefix}.visitorId`;
|
|
2284
|
-
const KEY_CONVERSATION = `${prefix}.conversationId`;
|
|
2285
|
-
const KEY_USER_PREFS = `${prefix}.userPrefs`;
|
|
2286
|
-
const KEY_PANEL_OPEN = `${prefix}.panelOpen`;
|
|
2287
|
-
const KEY_PANEL_SIZE = `${prefix}.panelSize`;
|
|
2288
|
-
const KEY_CALLOUT_DISMISSED = `${prefix}.calloutDismissed`;
|
|
2289
|
-
const KEY_SIDEBAR_COLLAPSED = `${prefix}.sidebarCollapsed`;
|
|
2290
|
-
const KEY_ACTIVE_MODULE = `${prefix}.activeModule`;
|
|
2291
|
-
const KEY_FORMS_DONE = `${prefix}.formsDone`;
|
|
2292
|
-
const persistence = {
|
|
2293
|
-
getVisitorId() {
|
|
2294
|
-
const existing = storage.get(KEY_VISITOR);
|
|
2295
|
-
if (existing) return existing;
|
|
2296
|
-
const minted = uuid7();
|
|
2297
|
-
storage.set(KEY_VISITOR, minted);
|
|
2298
|
-
return minted;
|
|
2299
|
-
},
|
|
2300
|
-
setVisitorId(id) {
|
|
2301
|
-
log6.debug("setVisitorId (rebind)", { id, widgetId });
|
|
2302
|
-
storage.set(KEY_VISITOR, id);
|
|
2303
|
-
},
|
|
2304
|
-
loadConversationId() {
|
|
2305
|
-
return storage.get(KEY_CONVERSATION) ?? void 0;
|
|
2306
|
-
},
|
|
2307
|
-
saveConversationId(id) {
|
|
2308
|
-
log6.trace("saveConversationId", { id, widgetId });
|
|
2309
|
-
if (id) storage.set(KEY_CONVERSATION, id);
|
|
2310
|
-
else storage.remove(KEY_CONVERSATION);
|
|
2311
|
-
},
|
|
2312
|
-
loadUserPrefs() {
|
|
2313
|
-
const raw = storage.get(KEY_USER_PREFS);
|
|
2314
|
-
if (!raw) return {};
|
|
2315
|
-
try {
|
|
2316
|
-
const parsed = JSON.parse(raw);
|
|
2317
|
-
return isPlainObject2(parsed) ? parsed : {};
|
|
2318
|
-
} catch (error) {
|
|
2319
|
-
log6.warn("loadUserPrefs parse failed", { error });
|
|
2320
|
-
return {};
|
|
2321
|
-
}
|
|
2322
|
-
},
|
|
2323
|
-
saveUserPrefs(s) {
|
|
2324
|
-
try {
|
|
2325
|
-
storage.set(KEY_USER_PREFS, JSON.stringify(s));
|
|
2326
|
-
} catch (error) {
|
|
2327
|
-
log6.warn("saveUserPrefs serialise failed", { error });
|
|
2328
|
-
}
|
|
2329
|
-
},
|
|
2330
|
-
patchUserPrefs(patch) {
|
|
2331
|
-
const current = persistence.loadUserPrefs();
|
|
2332
|
-
persistence.saveUserPrefs({ ...current, ...patch });
|
|
2333
|
-
},
|
|
2334
|
-
loadPanelOpen() {
|
|
2335
|
-
const raw = storage.get(KEY_PANEL_OPEN);
|
|
2336
|
-
if (raw === "1") return true;
|
|
2337
|
-
if (raw === "0") return false;
|
|
2338
|
-
return null;
|
|
2339
|
-
},
|
|
2340
|
-
savePanelOpen(open) {
|
|
2341
|
-
storage.set(KEY_PANEL_OPEN, open ? "1" : "0");
|
|
2342
|
-
},
|
|
2343
|
-
loadPanelSize() {
|
|
2344
|
-
const raw = storage.get(KEY_PANEL_SIZE);
|
|
2345
|
-
return raw === "normal" || raw === "expanded" || raw === "fullscreen" ? raw : null;
|
|
2346
|
-
},
|
|
2347
|
-
savePanelSize(size) {
|
|
2348
|
-
storage.set(KEY_PANEL_SIZE, size);
|
|
2349
|
-
},
|
|
2350
|
-
loadCalloutDismissed(currentText) {
|
|
2351
|
-
if (!currentText) return false;
|
|
2352
|
-
const dismissedText = storage.get(KEY_CALLOUT_DISMISSED);
|
|
2353
|
-
return !!dismissedText && dismissedText === currentText;
|
|
2354
|
-
},
|
|
2355
|
-
saveCalloutDismissed(currentText) {
|
|
2356
|
-
if (currentText) storage.set(KEY_CALLOUT_DISMISSED, currentText);
|
|
2357
|
-
else storage.remove(KEY_CALLOUT_DISMISSED);
|
|
2358
|
-
},
|
|
2359
|
-
loadSidebarCollapsed() {
|
|
2360
|
-
const raw = storage.get(KEY_SIDEBAR_COLLAPSED);
|
|
2361
|
-
if (raw === "1") return true;
|
|
2362
|
-
if (raw === "0") return false;
|
|
2363
|
-
return null;
|
|
2364
|
-
},
|
|
2365
|
-
saveSidebarCollapsed(collapsed) {
|
|
2366
|
-
storage.set(KEY_SIDEBAR_COLLAPSED, collapsed ? "1" : "0");
|
|
2367
|
-
},
|
|
2368
|
-
loadActiveModule() {
|
|
2369
|
-
return storage.get(KEY_ACTIVE_MODULE) || null;
|
|
2370
|
-
},
|
|
2371
|
-
saveActiveModule(id) {
|
|
2372
|
-
storage.set(KEY_ACTIVE_MODULE, id);
|
|
2373
|
-
},
|
|
2374
|
-
loadFormsDone() {
|
|
2375
|
-
const raw = storage.get(KEY_FORMS_DONE);
|
|
2376
|
-
if (!raw) return {};
|
|
2377
|
-
try {
|
|
2378
|
-
const parsed = JSON.parse(raw);
|
|
2379
|
-
return isPlainObject2(parsed) ? parsed : {};
|
|
2380
|
-
} catch (error) {
|
|
2381
|
-
log6.warn("loadFormsDone parse failed", { error });
|
|
2382
|
-
return {};
|
|
2383
|
-
}
|
|
2384
|
-
},
|
|
2385
|
-
saveFormDone(id) {
|
|
2386
|
-
try {
|
|
2387
|
-
const done = persistence.loadFormsDone();
|
|
2388
|
-
done[id] = Date.now();
|
|
2389
|
-
storage.set(KEY_FORMS_DONE, JSON.stringify(done));
|
|
2390
|
-
} catch (error) {
|
|
2391
|
-
log6.warn("saveFormDone serialise failed", { error });
|
|
2464
|
+
case "data-notification":
|
|
2465
|
+
return;
|
|
2466
|
+
default: {
|
|
2467
|
+
const _exhaustive = chunk;
|
|
2468
|
+
void _exhaustive;
|
|
2392
2469
|
}
|
|
2393
|
-
},
|
|
2394
|
-
clearConversation() {
|
|
2395
|
-
storage.remove(KEY_CONVERSATION);
|
|
2396
2470
|
}
|
|
2471
|
+
}
|
|
2472
|
+
/**
|
|
2473
|
+
* Terminal-state bump (`finish`/`error`). Touches the message's own `partsSig`
|
|
2474
|
+
* AND the list so subscribers re-render: a buffered bubble (subscribed to its
|
|
2475
|
+
* parts) flips out of the loading indicator into the finished reply, and the
|
|
2476
|
+
* list reflects the new `status`.
|
|
2477
|
+
*/
|
|
2478
|
+
bumpDone(m) {
|
|
2479
|
+
m.partsSig.value = [...m.partsSig.value];
|
|
2480
|
+
this.messagesSig.value = [...this.messagesSig.value];
|
|
2481
|
+
}
|
|
2482
|
+
};
|
|
2483
|
+
function ensureTextPart(m, kind, id) {
|
|
2484
|
+
const existing = m.partsSig.value.find((p33) => p33.kind === kind && p33.id === id);
|
|
2485
|
+
if (existing) return existing;
|
|
2486
|
+
const part = { kind, id, textSig: signal2(""), doneSig: signal2(false) };
|
|
2487
|
+
appendPart(m, part);
|
|
2488
|
+
return part;
|
|
2489
|
+
}
|
|
2490
|
+
function ensureToolPart(m, toolCallId, toolName) {
|
|
2491
|
+
const existing = m.partsSig.value.find((p33) => p33.kind === "tool" && p33.toolCallId === toolCallId);
|
|
2492
|
+
if (existing) return existing;
|
|
2493
|
+
const part = {
|
|
2494
|
+
kind: "tool",
|
|
2495
|
+
toolCallId,
|
|
2496
|
+
toolName: toolName ?? "tool",
|
|
2497
|
+
inputPartialSig: signal2(""),
|
|
2498
|
+
inputSig: signal2(void 0),
|
|
2499
|
+
outputSig: signal2(void 0),
|
|
2500
|
+
errorSig: signal2(void 0),
|
|
2501
|
+
stateSig: signal2("input"),
|
|
2502
|
+
approvalSig: signal2(void 0)
|
|
2397
2503
|
};
|
|
2398
|
-
|
|
2504
|
+
appendPart(m, part);
|
|
2505
|
+
return part;
|
|
2399
2506
|
}
|
|
2400
|
-
function
|
|
2401
|
-
|
|
2507
|
+
function appendPart(m, part) {
|
|
2508
|
+
m.partsSig.value = [...m.partsSig.value, part];
|
|
2509
|
+
}
|
|
2510
|
+
function applyTool(m, chunk) {
|
|
2511
|
+
switch (chunk.type) {
|
|
2512
|
+
case "tool-input-start":
|
|
2513
|
+
ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2514
|
+
return;
|
|
2515
|
+
case "tool-input-delta": {
|
|
2516
|
+
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2517
|
+
p33.inputPartialSig.value += chunk.delta;
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
case "tool-input-available": {
|
|
2521
|
+
const p33 = ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2522
|
+
p33.inputSig.value = chunk.input;
|
|
2523
|
+
p33.stateSig.value = isAskUserInputTool(p33.toolName) ? "awaiting-input" : "awaiting";
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
case "tool-approval-request": {
|
|
2527
|
+
const p33 = ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2528
|
+
p33.approvalSig.value = { id: chunk.approvalId };
|
|
2529
|
+
p33.stateSig.value = "awaiting-approval";
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
case "tool-output-available": {
|
|
2533
|
+
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2534
|
+
p33.outputSig.value = chunk.output;
|
|
2535
|
+
p33.stateSig.value = "output";
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
case "tool-output-error": {
|
|
2539
|
+
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2540
|
+
p33.errorSig.value = chunk.errorText;
|
|
2541
|
+
p33.stateSig.value = "error";
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
default:
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2402
2547
|
}
|
|
2403
2548
|
|
|
2404
2549
|
// src/core/feedback/index.ts
|
|
2405
2550
|
import { signal as signal3 } from "@preact/signals";
|
|
2406
2551
|
|
|
2407
2552
|
// src/core/feedback/audio.ts
|
|
2408
|
-
var
|
|
2553
|
+
var log8 = logger.scope("feedback:audio");
|
|
2409
2554
|
var AudioEngine = class {
|
|
2410
2555
|
constructor() {
|
|
2411
2556
|
__publicField(this, "ctx", null);
|
|
@@ -2426,7 +2571,7 @@ var AudioEngine = class {
|
|
|
2426
2571
|
this.master.gain.value = 1;
|
|
2427
2572
|
this.master.connect(this.ctx.destination);
|
|
2428
2573
|
} catch (err) {
|
|
2429
|
-
|
|
2574
|
+
log8.warn("AudioContext init failed", { err });
|
|
2430
2575
|
}
|
|
2431
2576
|
}
|
|
2432
2577
|
/** Route all sounds through this multiplier. Call on volume change. */
|
|
@@ -2483,7 +2628,7 @@ var AudioEngine = class {
|
|
|
2483
2628
|
this.buffers.set(url, buf);
|
|
2484
2629
|
return buf;
|
|
2485
2630
|
}).catch((err) => {
|
|
2486
|
-
|
|
2631
|
+
log8.warn("audio fetch/decode failed", { url, err });
|
|
2487
2632
|
this.buffers.delete(url);
|
|
2488
2633
|
return null;
|
|
2489
2634
|
});
|
|
@@ -2569,7 +2714,7 @@ function acquireHaptics() {
|
|
|
2569
2714
|
}
|
|
2570
2715
|
|
|
2571
2716
|
// src/core/feedback/index.ts
|
|
2572
|
-
var
|
|
2717
|
+
var log9 = logger.scope("feedback");
|
|
2573
2718
|
var DEDUP_WINDOW_MS = 100;
|
|
2574
2719
|
var FeedbackBus = class {
|
|
2575
2720
|
constructor(sound, haptics) {
|
|
@@ -2647,7 +2792,7 @@ var FeedbackBus = class {
|
|
|
2647
2792
|
const override = this.sound.events?.[event];
|
|
2648
2793
|
if (override === false) return;
|
|
2649
2794
|
if (typeof override === "string") {
|
|
2650
|
-
this.audio.playUrl(override).catch((err) =>
|
|
2795
|
+
this.audio.playUrl(override).catch((err) => log9.warn("audio play failed", { err, url: override }));
|
|
2651
2796
|
return;
|
|
2652
2797
|
}
|
|
2653
2798
|
const tone = DEFAULT_TONES[event];
|
|
@@ -3026,7 +3171,7 @@ function mountRawSvg(host, source) {
|
|
|
3026
3171
|
} catch {
|
|
3027
3172
|
}
|
|
3028
3173
|
}
|
|
3029
|
-
function LauncherCallout({ callout, launcherPosition, closeLabel, onDismiss }) {
|
|
3174
|
+
function LauncherCallout({ callout, launcherPosition, strings, closeLabel, onDismiss }) {
|
|
3030
3175
|
const [visible, setVisible] = useState(false);
|
|
3031
3176
|
useEffect(() => {
|
|
3032
3177
|
const id = setTimeout(() => setVisible(true), callout.delayMs);
|
|
@@ -3048,7 +3193,7 @@ function LauncherCallout({ callout, launcherPosition, closeLabel, onDismiss }) {
|
|
|
3048
3193
|
"data-animated": callout.animated ? "true" : void 0,
|
|
3049
3194
|
"data-testid": TID.callout,
|
|
3050
3195
|
children: [
|
|
3051
|
-
/* @__PURE__ */ jsx2("span", { children: callout.text }),
|
|
3196
|
+
/* @__PURE__ */ jsx2("span", { children: localizeText(strings, callout.text) }),
|
|
3052
3197
|
/* @__PURE__ */ jsx2(
|
|
3053
3198
|
"button",
|
|
3054
3199
|
{
|
|
@@ -3292,7 +3437,7 @@ function AgentBadge({ agent }) {
|
|
|
3292
3437
|
import { useEffect as useEffect4, useRef as useRef2, useState as useState2 } from "preact/hooks";
|
|
3293
3438
|
|
|
3294
3439
|
// src/core/attachments.ts
|
|
3295
|
-
var
|
|
3440
|
+
var log10 = logger.scope("attachments");
|
|
3296
3441
|
function ingest(source, existing, limits) {
|
|
3297
3442
|
const incoming = collectFiles(source);
|
|
3298
3443
|
const accepted = [];
|
|
@@ -3311,7 +3456,7 @@ function ingest(source, existing, limits) {
|
|
|
3311
3456
|
totalCount += 1;
|
|
3312
3457
|
}
|
|
3313
3458
|
if (accepted.length || rejected.length) {
|
|
3314
|
-
|
|
3459
|
+
log10.debug("ingest", {
|
|
3315
3460
|
accepted: accepted.length,
|
|
3316
3461
|
rejected: rejected.map((r) => ({ name: r.file.name, reason: r.reason }))
|
|
3317
3462
|
});
|
|
@@ -3322,7 +3467,7 @@ function revoke(att) {
|
|
|
3322
3467
|
try {
|
|
3323
3468
|
URL.revokeObjectURL(att.previewUrl);
|
|
3324
3469
|
} catch (error) {
|
|
3325
|
-
|
|
3470
|
+
log10.debug("revokeObjectURL failed", { error });
|
|
3326
3471
|
}
|
|
3327
3472
|
}
|
|
3328
3473
|
function reject(file, count, bytes, limits, accept) {
|
|
@@ -3498,7 +3643,7 @@ function pickSupportedMimeType() {
|
|
|
3498
3643
|
// src/ui/composer.tsx
|
|
3499
3644
|
import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
3500
3645
|
var p7 = BRAND.cssPrefix;
|
|
3501
|
-
var
|
|
3646
|
+
var log11 = logger.scope("composer");
|
|
3502
3647
|
function totalBytes(items) {
|
|
3503
3648
|
return items.reduce((acc, a) => acc + a.size, 0);
|
|
3504
3649
|
}
|
|
@@ -3551,7 +3696,7 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3551
3696
|
attachFromDrop: (items) => {
|
|
3552
3697
|
const result = ingest(items, attsRef.current, options.attachments);
|
|
3553
3698
|
if (result.accepted.length === 0) return;
|
|
3554
|
-
|
|
3699
|
+
log11.info("attach (drop)", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3555
3700
|
bus.emit("attach", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3556
3701
|
setAtts((prev) => [...prev, ...result.accepted]);
|
|
3557
3702
|
}
|
|
@@ -3572,7 +3717,7 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3572
3717
|
const result = ingest(data.items, attsRef.current, options.attachments);
|
|
3573
3718
|
if (result.accepted.length > 0) {
|
|
3574
3719
|
e.preventDefault();
|
|
3575
|
-
|
|
3720
|
+
log11.info("attach (paste)", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3576
3721
|
bus.emit("attach", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3577
3722
|
setAtts((prev) => [...prev, ...result.accepted]);
|
|
3578
3723
|
return;
|
|
@@ -3606,14 +3751,14 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3606
3751
|
const input = e.target;
|
|
3607
3752
|
const result = ingest(input.files, atts, options.attachments);
|
|
3608
3753
|
if (result.accepted.length > 0) {
|
|
3609
|
-
|
|
3754
|
+
log11.info("attach (file picker)", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3610
3755
|
bus.emit("attach", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3611
3756
|
setAtts((prev) => [...prev, ...result.accepted]);
|
|
3612
3757
|
}
|
|
3613
3758
|
input.value = "";
|
|
3614
3759
|
};
|
|
3615
3760
|
const removeAtt = (id) => {
|
|
3616
|
-
|
|
3761
|
+
log11.info("removeAttachment", { id });
|
|
3617
3762
|
bus.emit("removeAttachment", { id });
|
|
3618
3763
|
setAtts((prev) => {
|
|
3619
3764
|
const target = prev.find((a) => a.id === id);
|
|
@@ -3626,7 +3771,7 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3626
3771
|
if (!voice.isSupported) return;
|
|
3627
3772
|
if (voiceOn) {
|
|
3628
3773
|
const durationMs = voiceStartedAtRef.current ? Date.now() - voiceStartedAtRef.current : 0;
|
|
3629
|
-
|
|
3774
|
+
log11.info("voiceStop", { durationMs });
|
|
3630
3775
|
bus.emit("voiceStop", { durationMs });
|
|
3631
3776
|
voice.stop();
|
|
3632
3777
|
setVoiceOn(false);
|
|
@@ -3635,7 +3780,7 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3635
3780
|
return;
|
|
3636
3781
|
}
|
|
3637
3782
|
voiceStartedAtRef.current = Date.now();
|
|
3638
|
-
|
|
3783
|
+
log11.info("voiceStart");
|
|
3639
3784
|
bus.emit("voiceStart", void 0);
|
|
3640
3785
|
setVoiceOn(true);
|
|
3641
3786
|
feedback.play("voiceStart");
|
|
@@ -3645,7 +3790,7 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3645
3790
|
setVoiceOn(false);
|
|
3646
3791
|
if (voiceStartedAtRef.current) {
|
|
3647
3792
|
const durationMs = Date.now() - voiceStartedAtRef.current;
|
|
3648
|
-
|
|
3793
|
+
log11.debug("voiceStop (auto)", { durationMs });
|
|
3649
3794
|
bus.emit("voiceStop", { durationMs });
|
|
3650
3795
|
voiceStartedAtRef.current = null;
|
|
3651
3796
|
}
|
|
@@ -5206,7 +5351,7 @@ function startOfDay(ms) {
|
|
|
5206
5351
|
|
|
5207
5352
|
// src/ui/conversation-list.tsx
|
|
5208
5353
|
import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs15 } from "preact/jsx-runtime";
|
|
5209
|
-
var
|
|
5354
|
+
var log12 = logger.scope("history");
|
|
5210
5355
|
var BUCKET_TO_STRING = {
|
|
5211
5356
|
today: "dateToday",
|
|
5212
5357
|
yesterday: "dateYesterday",
|
|
@@ -5239,7 +5384,7 @@ function ConversationList({
|
|
|
5239
5384
|
setState("loaded");
|
|
5240
5385
|
}).catch((err) => {
|
|
5241
5386
|
if (cancelled) return;
|
|
5242
|
-
|
|
5387
|
+
log12.warn("listConversations failed", { err });
|
|
5243
5388
|
setState("error");
|
|
5244
5389
|
});
|
|
5245
5390
|
return () => {
|
|
@@ -5608,9 +5753,16 @@ function createController(depsRef) {
|
|
|
5608
5753
|
else if (form.frequency !== "always") depsRef.current.persistence.saveFormDone(form.id);
|
|
5609
5754
|
depsRef.current.onComplete(form, trigger, values, skipped);
|
|
5610
5755
|
};
|
|
5756
|
+
const refresh = () => {
|
|
5757
|
+
const active = activeForm.value;
|
|
5758
|
+
if (!active) return;
|
|
5759
|
+
const next = depsRef.current.forms.list.find((f) => f.id === active.form.id);
|
|
5760
|
+
if (next && next !== active.form) activeForm.value = { form: next, trigger: active.trigger };
|
|
5761
|
+
};
|
|
5611
5762
|
return {
|
|
5612
5763
|
activeForm,
|
|
5613
5764
|
fire,
|
|
5765
|
+
refresh,
|
|
5614
5766
|
complete: (values) => finish(values, false),
|
|
5615
5767
|
skip: () => finish({}, true)
|
|
5616
5768
|
};
|
|
@@ -5882,7 +6034,7 @@ function ModuleState({
|
|
|
5882
6034
|
// src/ui/modules/help.tsx
|
|
5883
6035
|
import { jsx as jsx27, jsxs as jsxs23 } from "preact/jsx-runtime";
|
|
5884
6036
|
var p23 = BRAND.cssPrefix;
|
|
5885
|
-
var
|
|
6037
|
+
var log13 = logger.scope("help");
|
|
5886
6038
|
var openArticle = (nav, a) => a.url ? nav.push({ kind: "iframe", url: a.url, title: a.title }) : nav.push({ kind: "content", id: a.id, title: a.title });
|
|
5887
6039
|
function groupByCategory(items) {
|
|
5888
6040
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -5936,7 +6088,7 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
|
|
|
5936
6088
|
setState("loaded");
|
|
5937
6089
|
}).catch((err) => {
|
|
5938
6090
|
if (cancelled) return;
|
|
5939
|
-
|
|
6091
|
+
log13.warn("listContent (help) failed", { err });
|
|
5940
6092
|
setErrorMsg(errorMessageFor(err, strings));
|
|
5941
6093
|
setState("error");
|
|
5942
6094
|
});
|
|
@@ -5987,7 +6139,7 @@ function HomeCard({ onClick, children, testid }) {
|
|
|
5987
6139
|
// src/ui/modules/home.tsx
|
|
5988
6140
|
import { Fragment as Fragment6, jsx as jsx29, jsxs as jsxs24 } from "preact/jsx-runtime";
|
|
5989
6141
|
var p25 = BRAND.cssPrefix;
|
|
5990
|
-
var
|
|
6142
|
+
var log14 = logger.scope("home");
|
|
5991
6143
|
function resolveGreeting(props2) {
|
|
5992
6144
|
const name = props2.options.userContext?.name;
|
|
5993
6145
|
const fromConfig = props2.config.greetingText;
|
|
@@ -6006,7 +6158,7 @@ function HomeRoot(props2) {
|
|
|
6006
6158
|
useEffect12(() => {
|
|
6007
6159
|
if (!config.showRecentConversations) return;
|
|
6008
6160
|
let cancelled = false;
|
|
6009
|
-
transport.listConversations({ limit: 1 }).then((res) => !cancelled && setRecent(res.conversations?.[0] ?? null)).catch((err) => !cancelled &&
|
|
6161
|
+
transport.listConversations({ limit: 1 }).then((res) => !cancelled && setRecent(res.conversations?.[0] ?? null)).catch((err) => !cancelled && log14.warn("listConversations (home) failed", { err }));
|
|
6010
6162
|
return () => {
|
|
6011
6163
|
cancelled = true;
|
|
6012
6164
|
};
|
|
@@ -6014,7 +6166,7 @@ function HomeRoot(props2) {
|
|
|
6014
6166
|
useEffect12(() => {
|
|
6015
6167
|
if (!config.contentTags?.length) return;
|
|
6016
6168
|
let cancelled = false;
|
|
6017
|
-
transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => !cancelled && setContent(res.items ?? [])).catch((err) => !cancelled &&
|
|
6169
|
+
transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => !cancelled && setContent(res.items ?? [])).catch((err) => !cancelled && log14.warn("listContent (home) failed", { err }));
|
|
6018
6170
|
return () => {
|
|
6019
6171
|
cancelled = true;
|
|
6020
6172
|
};
|
|
@@ -6022,7 +6174,7 @@ function HomeRoot(props2) {
|
|
|
6022
6174
|
const greeting = resolveGreeting(props2);
|
|
6023
6175
|
const avatars = config.userAvatars ?? [];
|
|
6024
6176
|
const status = config.status;
|
|
6025
|
-
const contentTitle = config.contentBlockTitle ?
|
|
6177
|
+
const contentTitle = config.contentBlockTitle ? localizeText(strings, config.contentBlockTitle) : strings.homeContentTitle;
|
|
6026
6178
|
return /* @__PURE__ */ jsx29("div", { class: `${p25}-module ${p25}-home`, "data-testid": TID.homeView, children: /* @__PURE__ */ jsxs24("div", { class: `${p25}-home-scroll`, children: [
|
|
6027
6179
|
/* @__PURE__ */ jsxs24("div", { class: `${p25}-home-hero`, children: [
|
|
6028
6180
|
/* @__PURE__ */ jsxs24("div", { class: `${p25}-home-hero-top`, children: [
|
|
@@ -6088,7 +6240,7 @@ var homeLayout = {
|
|
|
6088
6240
|
import { useEffect as useEffect13, useState as useState10 } from "preact/hooks";
|
|
6089
6241
|
import { jsx as jsx30, jsxs as jsxs25 } from "preact/jsx-runtime";
|
|
6090
6242
|
var p26 = BRAND.cssPrefix;
|
|
6091
|
-
var
|
|
6243
|
+
var log15 = logger.scope("news");
|
|
6092
6244
|
function NewsRoot({ transport, strings, config, nav, panelProps }) {
|
|
6093
6245
|
const tags = config.contentTags;
|
|
6094
6246
|
const [state, setState] = useState10("loading");
|
|
@@ -6104,7 +6256,7 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
|
|
|
6104
6256
|
setState("loaded");
|
|
6105
6257
|
}).catch((err) => {
|
|
6106
6258
|
if (cancelled) return;
|
|
6107
|
-
|
|
6259
|
+
log15.warn("listContent (news) failed", { err });
|
|
6108
6260
|
setErrorMsg(errorMessageFor(err, strings));
|
|
6109
6261
|
setState("error");
|
|
6110
6262
|
});
|
|
@@ -6177,7 +6329,7 @@ function HomeTabBar({ modules, activeTab, strings, unreadCount, onSelect }) {
|
|
|
6177
6329
|
/* @__PURE__ */ jsx31(Icon, {}),
|
|
6178
6330
|
badge ? /* @__PURE__ */ jsx31("span", { class: `${p27}-tab-badge`, "data-testid": TID.tabBadge, children: badge }) : null
|
|
6179
6331
|
] }),
|
|
6180
|
-
/* @__PURE__ */ jsx31("span", { class: `${p27}-tab-label`, children:
|
|
6332
|
+
/* @__PURE__ */ jsx31("span", { class: `${p27}-tab-label`, children: localizeText(strings, m.label) })
|
|
6181
6333
|
]
|
|
6182
6334
|
},
|
|
6183
6335
|
m.id
|
|
@@ -6219,7 +6371,7 @@ function IframeView({ url, title, strings, onBack, actions }) {
|
|
|
6219
6371
|
import { useCallback as useCallback3, useEffect as useEffect14, useState as useState11 } from "preact/hooks";
|
|
6220
6372
|
import { jsx as jsx33, jsxs as jsxs28 } from "preact/jsx-runtime";
|
|
6221
6373
|
var p29 = BRAND.cssPrefix;
|
|
6222
|
-
var
|
|
6374
|
+
var log16 = logger.scope("content");
|
|
6223
6375
|
function ContentView({ id, title, transport, strings, onBack, actions }) {
|
|
6224
6376
|
const [item, setItem] = useState11(null);
|
|
6225
6377
|
const [failed, setFailed] = useState11(false);
|
|
@@ -6238,7 +6390,7 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
|
|
|
6238
6390
|
else setFailed(true);
|
|
6239
6391
|
}).catch((err) => {
|
|
6240
6392
|
if (cancelled) return;
|
|
6241
|
-
|
|
6393
|
+
log16.warn("listContent (reader) failed", { err });
|
|
6242
6394
|
setFailed(true);
|
|
6243
6395
|
});
|
|
6244
6396
|
return () => {
|
|
@@ -6458,7 +6610,7 @@ function useLauncherCallout({ callout, persistence }) {
|
|
|
6458
6610
|
|
|
6459
6611
|
// src/ui/app.tsx
|
|
6460
6612
|
import { jsx as jsx36, jsxs as jsxs31 } from "preact/jsx-runtime";
|
|
6461
|
-
var
|
|
6613
|
+
var log17 = logger.scope("app");
|
|
6462
6614
|
var p32 = BRAND.cssPrefix;
|
|
6463
6615
|
function App({ options, hostElement, bus }) {
|
|
6464
6616
|
const [persistence] = useState13(() => createPersistence(options.widgetId, options.storage));
|
|
@@ -6516,7 +6668,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6516
6668
|
});
|
|
6517
6669
|
const calloutPersistent = options.launcher.callout?.persistent ?? true;
|
|
6518
6670
|
const dismissCallout = useCallback6(() => {
|
|
6519
|
-
|
|
6671
|
+
log17.info("calloutDismiss");
|
|
6520
6672
|
bus.emit("calloutDismiss", void 0);
|
|
6521
6673
|
dismissCalloutRaw();
|
|
6522
6674
|
}, [bus, dismissCalloutRaw]);
|
|
@@ -6550,7 +6702,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6550
6702
|
(patch) => {
|
|
6551
6703
|
persistence.patchUserPrefs(patch);
|
|
6552
6704
|
transport.saveUserPrefs(persistence.loadUserPrefs()).catch((err) => {
|
|
6553
|
-
|
|
6705
|
+
log17.warn("saveUserPrefs failed", { err });
|
|
6554
6706
|
});
|
|
6555
6707
|
},
|
|
6556
6708
|
[persistence, transport]
|
|
@@ -6631,7 +6783,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6631
6783
|
values: rec.values
|
|
6632
6784
|
}));
|
|
6633
6785
|
} catch (err) {
|
|
6634
|
-
|
|
6786
|
+
log17.debug("bootstrap failed \u2014 no form markers (non-fatal)", { err });
|
|
6635
6787
|
return [];
|
|
6636
6788
|
}
|
|
6637
6789
|
},
|
|
@@ -6671,7 +6823,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6671
6823
|
}
|
|
6672
6824
|
} catch (err) {
|
|
6673
6825
|
if (isStale()) return;
|
|
6674
|
-
|
|
6826
|
+
log17.warn("loadThread failed; resetting conversationId", { err, conversationId });
|
|
6675
6827
|
conversationIdSig.value = void 0;
|
|
6676
6828
|
persistence.saveConversationId(void 0);
|
|
6677
6829
|
} finally {
|
|
@@ -6710,7 +6862,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6710
6862
|
}
|
|
6711
6863
|
if (isStale()) return;
|
|
6712
6864
|
if (res.visitorId && res.visitorId !== visitorId) {
|
|
6713
|
-
|
|
6865
|
+
log17.info("visitor rebound", { previous: visitorId, current: res.visitorId, reason: res.rebind?.visitorId });
|
|
6714
6866
|
persistence.setVisitorId(res.visitorId);
|
|
6715
6867
|
setVisitorId(res.visitorId);
|
|
6716
6868
|
bus.emit("visitorRebound", {
|
|
@@ -6720,7 +6872,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6720
6872
|
});
|
|
6721
6873
|
}
|
|
6722
6874
|
if (res.conversationId && res.conversationId !== conversationId) {
|
|
6723
|
-
|
|
6875
|
+
log17.info("conversation rebound", {
|
|
6724
6876
|
previous: conversationId,
|
|
6725
6877
|
current: res.conversationId,
|
|
6726
6878
|
reason: res.rebind?.conversationId
|
|
@@ -6735,9 +6887,9 @@ function App({ options, hostElement, bus }) {
|
|
|
6735
6887
|
if (res.welcome?.suggestions) setSuggestions(res.welcome.suggestions);
|
|
6736
6888
|
if (options.mode === "page") {
|
|
6737
6889
|
if (res.site && isSiteConfigShape(res.site)) setParsedSite(res.site);
|
|
6738
|
-
else if (res.site)
|
|
6890
|
+
else if (res.site) log17.warn("invalid site config, using fallback", { site: res.site });
|
|
6739
6891
|
if (res.blocks && isBlocksConfigShape(res.blocks)) setParsedBlocks(res.blocks);
|
|
6740
|
-
else if (res.blocks)
|
|
6892
|
+
else if (res.blocks) log17.warn("invalid blocks config, using fallback", { blocks: res.blocks });
|
|
6741
6893
|
}
|
|
6742
6894
|
if (res.userPrefs && !newConversation) {
|
|
6743
6895
|
persistence.saveUserPrefs(res.userPrefs);
|
|
@@ -6803,7 +6955,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6803
6955
|
resumeActiveRef.current = true;
|
|
6804
6956
|
const chatTab = chatTabIdRef.current;
|
|
6805
6957
|
if (chatTab) homeNav.switchTab(chatTab);
|
|
6806
|
-
|
|
6958
|
+
log17.info("resumed in-flight reply on mount");
|
|
6807
6959
|
}
|
|
6808
6960
|
reducer.apply(evt.chunk);
|
|
6809
6961
|
if (evt.chunk.type === "finish" && evt.chunk.canContinue === false) setCanSend(false);
|
|
@@ -6819,7 +6971,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6819
6971
|
bubble.errorText = errorMessageFor(err, options.strings);
|
|
6820
6972
|
feedback.play("error");
|
|
6821
6973
|
}
|
|
6822
|
-
|
|
6974
|
+
log17.debug("mount resume ended without completing", { err });
|
|
6823
6975
|
} finally {
|
|
6824
6976
|
resumeBubbleRef.current = null;
|
|
6825
6977
|
if (bubble) {
|
|
@@ -6848,9 +7000,9 @@ function App({ options, hostElement, bus }) {
|
|
|
6848
7000
|
void (async () => {
|
|
6849
7001
|
try {
|
|
6850
7002
|
const res = await dataBootRef.current;
|
|
6851
|
-
setRemoteForms(resolveForms((res ?? { forms: [] }).forms));
|
|
7003
|
+
setRemoteForms(resolveForms((res ?? { forms: [] }).forms, effectiveLocale));
|
|
6852
7004
|
} catch (err) {
|
|
6853
|
-
|
|
7005
|
+
log17.debug("bootstrap failed \u2014 treating as no forms", { err });
|
|
6854
7006
|
} finally {
|
|
6855
7007
|
setFormsReady(true);
|
|
6856
7008
|
}
|
|
@@ -6866,6 +7018,23 @@ function App({ options, hostElement, bus }) {
|
|
|
6866
7018
|
void runResume(handle);
|
|
6867
7019
|
}
|
|
6868
7020
|
}, [activated]);
|
|
7021
|
+
const formsLocaleRef = useRef9(null);
|
|
7022
|
+
useEffect17(() => {
|
|
7023
|
+
if (!activated) return;
|
|
7024
|
+
if (formsLocaleRef.current === null) {
|
|
7025
|
+
formsLocaleRef.current = effectiveLocale;
|
|
7026
|
+
return;
|
|
7027
|
+
}
|
|
7028
|
+
if (formsLocaleRef.current === effectiveLocale) return;
|
|
7029
|
+
formsLocaleRef.current = effectiveLocale;
|
|
7030
|
+
let cancelled = false;
|
|
7031
|
+
void transport.bootstrap().then((res) => {
|
|
7032
|
+
if (!cancelled) setRemoteForms(resolveForms((res ?? { forms: [] }).forms, effectiveLocale));
|
|
7033
|
+
}).catch((err) => log17.debug("forms re-localize failed \u2014 keeping current copy", { err }));
|
|
7034
|
+
return () => {
|
|
7035
|
+
cancelled = true;
|
|
7036
|
+
};
|
|
7037
|
+
}, [activated, effectiveLocale, transport]);
|
|
6869
7038
|
const lastUserSig = useRef9(userSig);
|
|
6870
7039
|
useEffect17(() => {
|
|
6871
7040
|
if (!conversationReady || lastUserSig.current === userSig) return;
|
|
@@ -6950,7 +7119,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6950
7119
|
(pt) => pt.kind === "tool" && pt.toolCallId === toolCallId
|
|
6951
7120
|
);
|
|
6952
7121
|
if (!part) {
|
|
6953
|
-
|
|
7122
|
+
log17.warn("resolveTool: tool part not found", { toolCallId });
|
|
6954
7123
|
return;
|
|
6955
7124
|
}
|
|
6956
7125
|
mutate(part);
|
|
@@ -6964,7 +7133,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6964
7133
|
);
|
|
6965
7134
|
const handleToolResult = useCallback6(
|
|
6966
7135
|
(toolCallId, output) => {
|
|
6967
|
-
|
|
7136
|
+
log17.info("toolResult", { toolCallId });
|
|
6968
7137
|
bus.emit("toolResult", { toolCallId });
|
|
6969
7138
|
resolveTool(toolCallId, TRIGGER.toolResult, (part) => {
|
|
6970
7139
|
part.outputSig.value = output;
|
|
@@ -6975,7 +7144,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6975
7144
|
);
|
|
6976
7145
|
const handleToolDecision = useCallback6(
|
|
6977
7146
|
(toolCallId, approved, reason) => {
|
|
6978
|
-
|
|
7147
|
+
log17.info("toolDecision", { toolCallId, approved });
|
|
6979
7148
|
bus.emit("toolDecision", { toolCallId, approved });
|
|
6980
7149
|
resolveTool(toolCallId, TRIGGER.toolApproval, (part) => {
|
|
6981
7150
|
const id = part.approvalSig.value?.id ?? toolCallId;
|
|
@@ -6999,7 +7168,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6999
7168
|
pageArea: () => options.pageContext?.area ? String(options.pageContext.area) : void 0,
|
|
7000
7169
|
conversationId: () => conversationIdSig.value,
|
|
7001
7170
|
onComplete: (form, trigger, values, skipped) => {
|
|
7002
|
-
|
|
7171
|
+
log17.info("formSubmit", { formId: form.id, trigger, skipped });
|
|
7003
7172
|
bus.emit("formSubmit", { formId: form.id, values, skipped });
|
|
7004
7173
|
setFormMarkers((prev) => [
|
|
7005
7174
|
...prev.filter((m) => !(m.formId === form.id && m.outcome === "skipped")),
|
|
@@ -7022,6 +7191,9 @@ function App({ options, hostElement, bus }) {
|
|
|
7022
7191
|
}
|
|
7023
7192
|
});
|
|
7024
7193
|
const activeForm = useComputed7(() => forms.activeForm.value);
|
|
7194
|
+
useEffect17(() => {
|
|
7195
|
+
forms.refresh();
|
|
7196
|
+
}, [effectiveForms, forms]);
|
|
7025
7197
|
const pageArea = options.pageContext?.area ? String(options.pageContext.area) : void 0;
|
|
7026
7198
|
const msgCount = useComputed7(() => messagesSig.value.length);
|
|
7027
7199
|
useEffect17(() => {
|
|
@@ -7044,12 +7216,12 @@ function App({ options, hostElement, bus }) {
|
|
|
7044
7216
|
}, [idleMs, isOpen, forms, msgCount.value]);
|
|
7045
7217
|
const handleSend = useCallback6(
|
|
7046
7218
|
async (text, attachments) => {
|
|
7047
|
-
|
|
7219
|
+
log17.info("send", { textLen: text.length, attachments: attachments.length, streaming });
|
|
7048
7220
|
if (streaming) return;
|
|
7049
7221
|
bus.emit("send", { text, attachmentCount: attachments.length });
|
|
7050
7222
|
if (suggestions.length > 0) setSuggestions([]);
|
|
7051
7223
|
const uploaded = await uploadAttachments(transport, attachments, (err) => {
|
|
7052
|
-
|
|
7224
|
+
log17.warn("upload failed", { err });
|
|
7053
7225
|
bus.emit("error", err);
|
|
7054
7226
|
options.onError?.(err);
|
|
7055
7227
|
});
|
|
@@ -7066,24 +7238,24 @@ function App({ options, hostElement, bus }) {
|
|
|
7066
7238
|
[streaming, transport, bus, options, suggestions.length, messagesSig, reducer, streamAssistant, forms]
|
|
7067
7239
|
);
|
|
7068
7240
|
const handleStop = useCallback6(() => {
|
|
7069
|
-
|
|
7241
|
+
log17.info("stop");
|
|
7070
7242
|
bus.emit("stop", void 0);
|
|
7071
7243
|
activeCancel?.();
|
|
7072
7244
|
}, [activeCancel, bus]);
|
|
7073
7245
|
const handleSoundToggle = useCallback6(() => {
|
|
7074
7246
|
feedback.toggleMuted();
|
|
7075
7247
|
const muted = feedback.mutedSig.value;
|
|
7076
|
-
|
|
7248
|
+
log17.info("soundToggle", { muted });
|
|
7077
7249
|
bus.emit("soundToggle", { muted });
|
|
7078
7250
|
}, [feedback, bus]);
|
|
7079
7251
|
const handleClose = useCallback6(() => {
|
|
7080
7252
|
if (resolveCloseAction(options.mode, panelSize) === "exit-fullscreen") {
|
|
7081
|
-
|
|
7253
|
+
log17.info("close \u2192 exit-fullscreen", { mode: options.mode });
|
|
7082
7254
|
setPanelSize("normal");
|
|
7083
7255
|
bus.emit("fullscreen", false);
|
|
7084
7256
|
return;
|
|
7085
7257
|
}
|
|
7086
|
-
|
|
7258
|
+
log17.info("close \u2192 panel closed", { mode: options.mode });
|
|
7087
7259
|
forms.fire("panel-close");
|
|
7088
7260
|
setIsOpen(false);
|
|
7089
7261
|
persistence.savePanelOpen(false);
|
|
@@ -7094,7 +7266,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7094
7266
|
if (!options.modules.list.some((m) => m.layout === "chat")) return;
|
|
7095
7267
|
transport.listConversations({ limit: 50 }).then((res) => {
|
|
7096
7268
|
unreadCountSig.value = res.conversations.reduce((sum, c) => sum + (c.unreadCount ?? 0), 0);
|
|
7097
|
-
}).catch((err) =>
|
|
7269
|
+
}).catch((err) => log17.debug("refreshUnread failed (non-fatal)", { err }));
|
|
7098
7270
|
}, [transport, options.modules, unreadCountSig]);
|
|
7099
7271
|
const unreadSeeded = useRef9(false);
|
|
7100
7272
|
useEffect17(() => {
|
|
@@ -7103,7 +7275,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7103
7275
|
refreshUnread();
|
|
7104
7276
|
}, [activated, conversationReady, refreshUnread]);
|
|
7105
7277
|
const handleOpen = useCallback6(() => {
|
|
7106
|
-
|
|
7278
|
+
log17.info("open", { mode: options.mode });
|
|
7107
7279
|
feedback.unlock();
|
|
7108
7280
|
setLauncherLeaving(true);
|
|
7109
7281
|
setIsOpen(true);
|
|
@@ -7117,11 +7289,11 @@ function App({ options, hostElement, bus }) {
|
|
|
7117
7289
|
const handleClear = useCallback6(() => {
|
|
7118
7290
|
const hasUserMessages = messagesSig.value.some((m) => m.role === "user");
|
|
7119
7291
|
if (!hasUserMessages && canSend && conversationIdSig.value) {
|
|
7120
|
-
|
|
7292
|
+
log17.info("clear \u2192 reusing empty conversation", { conversationId: conversationIdSig.value });
|
|
7121
7293
|
if (messagesSig.value.length === 0) playWelcome(welcomeRef.current?.messages);
|
|
7122
7294
|
return;
|
|
7123
7295
|
}
|
|
7124
|
-
|
|
7296
|
+
log17.info("clear \u2192 new conversation", { wasStreaming: streaming });
|
|
7125
7297
|
bus.emit("clear", void 0);
|
|
7126
7298
|
if (streaming) activeCancel?.();
|
|
7127
7299
|
messagesSig.value = [];
|
|
@@ -7139,14 +7311,14 @@ function App({ options, hostElement, bus }) {
|
|
|
7139
7311
|
}, [handleClear, homeNav, options]);
|
|
7140
7312
|
const handleExpand = useCallback6(() => {
|
|
7141
7313
|
const nextSize = panelSize === "expanded" ? "normal" : "expanded";
|
|
7142
|
-
|
|
7314
|
+
log17.info("expand", { from: panelSize, expanded: nextSize === "expanded" });
|
|
7143
7315
|
setPanelSize(nextSize);
|
|
7144
7316
|
persistence.savePanelSize(nextSize);
|
|
7145
7317
|
bus.emit("expand", nextSize === "expanded");
|
|
7146
7318
|
}, [bus, panelSize, persistence]);
|
|
7147
7319
|
const handleFullscreen = useCallback6(() => {
|
|
7148
7320
|
const nextSize = panelSize === "fullscreen" ? "normal" : "fullscreen";
|
|
7149
|
-
|
|
7321
|
+
log17.info("fullscreen", { from: panelSize, fullscreen: nextSize === "fullscreen" });
|
|
7150
7322
|
setPanelSize(nextSize);
|
|
7151
7323
|
persistence.savePanelSize(nextSize);
|
|
7152
7324
|
bus.emit("fullscreen", nextSize === "fullscreen");
|
|
@@ -7156,21 +7328,21 @@ function App({ options, hostElement, bus }) {
|
|
|
7156
7328
|
const url = new URL(options.popOutUrl);
|
|
7157
7329
|
if (conversationIdSig.value) url.searchParams.set("conversation", conversationIdSig.value);
|
|
7158
7330
|
if (options.widgetId !== "default") url.searchParams.set("widgetId", options.widgetId);
|
|
7159
|
-
|
|
7331
|
+
log17.info("popOut", { url: url.toString() });
|
|
7160
7332
|
window.open(url.toString(), "_blank", "noopener,noreferrer");
|
|
7161
7333
|
bus.emit("popOut", void 0);
|
|
7162
7334
|
}, [bus, conversationIdSig, options.popOutUrl, options.widgetId]);
|
|
7163
7335
|
const handleToggleHistory = useCallback6(() => {
|
|
7164
7336
|
setView((v) => {
|
|
7165
7337
|
const next = v === "history" ? "chat" : "history";
|
|
7166
|
-
|
|
7338
|
+
log17.info("toggleHistory", { view: next });
|
|
7167
7339
|
bus.emit("toggleHistory", { view: next });
|
|
7168
7340
|
return next;
|
|
7169
7341
|
});
|
|
7170
7342
|
}, [bus]);
|
|
7171
7343
|
const handleLocaleChange = useCallback6(
|
|
7172
7344
|
(locale) => {
|
|
7173
|
-
|
|
7345
|
+
log17.info("localeChange", { locale });
|
|
7174
7346
|
setActiveLocale(locale);
|
|
7175
7347
|
patchAndSync({ locale });
|
|
7176
7348
|
bus.emit("localeChange", locale);
|
|
@@ -7179,7 +7351,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7179
7351
|
);
|
|
7180
7352
|
const handleThemeChange = useCallback6(
|
|
7181
7353
|
(mode) => {
|
|
7182
|
-
|
|
7354
|
+
log17.info("themeChange", { mode });
|
|
7183
7355
|
setActiveThemeMode(mode);
|
|
7184
7356
|
hostElement.dataset.theme = mode;
|
|
7185
7357
|
patchAndSync({ themeMode: mode });
|
|
@@ -7189,7 +7361,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7189
7361
|
);
|
|
7190
7362
|
const handleTextSizeChange = useCallback6(
|
|
7191
7363
|
(size) => {
|
|
7192
|
-
|
|
7364
|
+
log17.info("textSizeChange", { size });
|
|
7193
7365
|
setActiveTextSize(size);
|
|
7194
7366
|
hostElement.dataset.textSize = size;
|
|
7195
7367
|
patchAndSync({ textSize: size });
|
|
@@ -7200,7 +7372,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7200
7372
|
const handleToggleSidebarCollapsed = useCallback6(() => {
|
|
7201
7373
|
setSidebarCollapsed((prev) => {
|
|
7202
7374
|
const next = !prev;
|
|
7203
|
-
|
|
7375
|
+
log17.info("sidebarToggle", { collapsed: next });
|
|
7204
7376
|
persistence.saveSidebarCollapsed(next);
|
|
7205
7377
|
bus.emit("sidebarToggle", { collapsed: next });
|
|
7206
7378
|
return next;
|
|
@@ -7208,7 +7380,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7208
7380
|
}, [persistence, bus]);
|
|
7209
7381
|
const handleWidgetSizeChange = useCallback6(
|
|
7210
7382
|
(size) => {
|
|
7211
|
-
|
|
7383
|
+
log17.info("resize", size);
|
|
7212
7384
|
patchAndSync({ widgetSize: size });
|
|
7213
7385
|
bus.emit("resize", size);
|
|
7214
7386
|
},
|
|
@@ -7216,7 +7388,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7216
7388
|
);
|
|
7217
7389
|
const handleSelectHistoryConversation = useCallback6(
|
|
7218
7390
|
async (targetConversationId) => {
|
|
7219
|
-
|
|
7391
|
+
log17.info("selectConversation", { conversationId: targetConversationId });
|
|
7220
7392
|
bus.emit("selectConversation", { conversationId: targetConversationId });
|
|
7221
7393
|
try {
|
|
7222
7394
|
const [res, markers] = await Promise.all([
|
|
@@ -7318,7 +7490,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7318
7490
|
onStop: handleStop,
|
|
7319
7491
|
onSuggestion: (s) => {
|
|
7320
7492
|
const text = s.text ?? s.label;
|
|
7321
|
-
|
|
7493
|
+
log17.info("suggestion", { text });
|
|
7322
7494
|
bus.emit("suggestion", { text });
|
|
7323
7495
|
void handleSend(text, []);
|
|
7324
7496
|
},
|
|
@@ -7408,6 +7580,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7408
7580
|
{
|
|
7409
7581
|
callout: calloutToRender,
|
|
7410
7582
|
launcherPosition: effectiveOptions.position,
|
|
7583
|
+
strings: effectiveOptions.strings,
|
|
7411
7584
|
closeLabel: effectiveOptions.strings.close,
|
|
7412
7585
|
onDismiss: dismissCallout
|
|
7413
7586
|
}
|
|
@@ -7440,14 +7613,14 @@ function emitMessage(bus, options, role, text) {
|
|
|
7440
7613
|
|
|
7441
7614
|
// src/ui/error-boundary.tsx
|
|
7442
7615
|
import { Component } from "preact";
|
|
7443
|
-
var
|
|
7616
|
+
var log18 = logger.scope("error-boundary");
|
|
7444
7617
|
var ErrorBoundary = class extends Component {
|
|
7445
7618
|
constructor() {
|
|
7446
7619
|
super(...arguments);
|
|
7447
7620
|
__publicField(this, "state", { crashed: false });
|
|
7448
7621
|
}
|
|
7449
7622
|
componentDidCatch(error, info) {
|
|
7450
|
-
|
|
7623
|
+
log18.error("widget render crashed", { error, componentStack: info?.componentStack });
|
|
7451
7624
|
this.setState({ crashed: true });
|
|
7452
7625
|
this.props.onError(error, info?.componentStack);
|
|
7453
7626
|
}
|
|
@@ -7456,102 +7629,6 @@ var ErrorBoundary = class extends Component {
|
|
|
7456
7629
|
}
|
|
7457
7630
|
};
|
|
7458
7631
|
|
|
7459
|
-
// src/shadow/iframe-mount.ts
|
|
7460
|
-
var log18 = logger.scope("iframe");
|
|
7461
|
-
var IFRAME_SRCDOC = '<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;padding:0;background:transparent;overflow:hidden}</style></head><body></body></html>';
|
|
7462
|
-
function createIframeMount(opts, position, initialMode = "closed", layoutMode = "floating", target = null) {
|
|
7463
|
-
log18.debug("createIframeMount", { position, initialMode, layoutMode, hasTarget: !!target });
|
|
7464
|
-
const iframe = document.createElement("iframe");
|
|
7465
|
-
iframe.title = `${BRAND.name} chat`;
|
|
7466
|
-
iframe.setAttribute("allow", "microphone; clipboard-write");
|
|
7467
|
-
sizeIframe(iframe, position, initialMode, layoutMode);
|
|
7468
|
-
const parent = layoutMode === "inline" && target ? target : document.body;
|
|
7469
|
-
parent.appendChild(iframe);
|
|
7470
|
-
const doc = iframe.contentDocument;
|
|
7471
|
-
if (!doc) throw new Error("iframe contentDocument unavailable (cross-origin?)");
|
|
7472
|
-
doc.open();
|
|
7473
|
-
doc.write(IFRAME_SRCDOC);
|
|
7474
|
-
doc.close();
|
|
7475
|
-
const mount2 = createShadowMount({ ...opts, target: doc.body });
|
|
7476
|
-
const mo = new MutationObserver(() => {
|
|
7477
|
-
sizeIframe(iframe, position, mount2.hostElement.dataset.mode ?? "closed", layoutMode);
|
|
7478
|
-
});
|
|
7479
|
-
mo.observe(mount2.hostElement, { attributes: true, attributeFilter: ["data-mode"] });
|
|
7480
|
-
return {
|
|
7481
|
-
...mount2,
|
|
7482
|
-
iframeElement: iframe,
|
|
7483
|
-
destroy() {
|
|
7484
|
-
mo.disconnect();
|
|
7485
|
-
mount2.destroy();
|
|
7486
|
-
iframe.remove();
|
|
7487
|
-
}
|
|
7488
|
-
};
|
|
7489
|
-
}
|
|
7490
|
-
var BASE_CSS = "border:0;background:transparent;z-index:2147483647;pointer-events:auto;";
|
|
7491
|
-
var FLOATING_SIZE_BY_MODE = {
|
|
7492
|
-
closed: { w: "88px", h: "88px" },
|
|
7493
|
-
expanded: { w: "min(calc(100vw - 32px), 680px)", h: "min(calc(100vh - 32px), 860px)" },
|
|
7494
|
-
// `open` (normal) — also the fallback for any other non-fullscreen state.
|
|
7495
|
-
open: { w: "min(calc(100vw - 32px), 440px)", h: "min(calc(100vh - 32px), 700px)" }
|
|
7496
|
-
};
|
|
7497
|
-
function sizeIframe(iframe, position, mode, layoutMode) {
|
|
7498
|
-
if (mode === "fullscreen" || layoutMode === "standalone") {
|
|
7499
|
-
iframe.style.cssText = `${BASE_CSS}position:fixed;inset:0;width:100vw;height:100vh;`;
|
|
7500
|
-
return;
|
|
7501
|
-
}
|
|
7502
|
-
if (layoutMode === "inline") {
|
|
7503
|
-
iframe.style.cssText = `${BASE_CSS}position:static;display:block;width:100%;height:100%;`;
|
|
7504
|
-
return;
|
|
7505
|
-
}
|
|
7506
|
-
const compact = typeof matchMedia === "function" && matchMedia("(max-width: 640px)").matches;
|
|
7507
|
-
if (compact && (mode === "open" || mode === "expanded")) {
|
|
7508
|
-
iframe.style.cssText = `${BASE_CSS}position:fixed;inset:0;width:100vw;height:100vh;`;
|
|
7509
|
-
return;
|
|
7510
|
-
}
|
|
7511
|
-
const vKey = position.startsWith("top-") ? "top" : "bottom";
|
|
7512
|
-
const hKey = position.endsWith("-left") ? "left" : "right";
|
|
7513
|
-
const { w, h: h2 } = FLOATING_SIZE_BY_MODE[mode] ?? FLOATING_SIZE_BY_MODE.open;
|
|
7514
|
-
iframe.style.cssText = `${BASE_CSS}position:fixed;${vKey}:16px;${hKey}:16px;width:${w};height:${h2};`;
|
|
7515
|
-
}
|
|
7516
|
-
|
|
7517
|
-
// src/core/events.ts
|
|
7518
|
-
var EventBus = class {
|
|
7519
|
-
constructor() {
|
|
7520
|
-
__publicField(this, "handlers", /* @__PURE__ */ new Map());
|
|
7521
|
-
}
|
|
7522
|
-
/**
|
|
7523
|
-
* Register a listener for `name`.
|
|
7524
|
-
* @returns an `off` function that removes this listener.
|
|
7525
|
-
*/
|
|
7526
|
-
on(name, fn) {
|
|
7527
|
-
let set = this.handlers.get(name);
|
|
7528
|
-
if (!set) {
|
|
7529
|
-
set = /* @__PURE__ */ new Set();
|
|
7530
|
-
this.handlers.set(name, set);
|
|
7531
|
-
}
|
|
7532
|
-
set.add(fn);
|
|
7533
|
-
return () => this.handlers.get(name)?.delete(fn);
|
|
7534
|
-
}
|
|
7535
|
-
/**
|
|
7536
|
-
* Dispatch an event synchronously to all listeners.
|
|
7537
|
-
* Listener errors are swallowed — the widget must not crash on user callbacks.
|
|
7538
|
-
*/
|
|
7539
|
-
emit(name, payload) {
|
|
7540
|
-
const set = this.handlers.get(name);
|
|
7541
|
-
if (!set) return;
|
|
7542
|
-
for (const fn of set) {
|
|
7543
|
-
try {
|
|
7544
|
-
fn(payload);
|
|
7545
|
-
} catch {
|
|
7546
|
-
}
|
|
7547
|
-
}
|
|
7548
|
-
}
|
|
7549
|
-
/** Remove every listener. Called by the instance `destroy()`. */
|
|
7550
|
-
clear() {
|
|
7551
|
-
this.handlers.clear();
|
|
7552
|
-
}
|
|
7553
|
-
};
|
|
7554
|
-
|
|
7555
7632
|
// src/core/tracking.ts
|
|
7556
7633
|
var PROTOCOL_VERSION = "1";
|
|
7557
7634
|
var MAX_HITS_PER_MINUTE = 60;
|
|
@@ -7667,6 +7744,73 @@ function createTracker(bus, deps) {
|
|
|
7667
7744
|
};
|
|
7668
7745
|
}
|
|
7669
7746
|
|
|
7747
|
+
// src/core/boot.ts
|
|
7748
|
+
function createWidgetRuntime(params) {
|
|
7749
|
+
const { bus, host, appRoot, persistence, reportError } = params;
|
|
7750
|
+
let userRaw = params.initialRaw;
|
|
7751
|
+
let serverConfig;
|
|
7752
|
+
let currentOptions = resolveOptions(userRaw);
|
|
7753
|
+
let firstRender = true;
|
|
7754
|
+
const recompute = () => {
|
|
7755
|
+
currentOptions = resolveOptions(serverConfig ? mergeServerConfig(userRaw, serverConfig) : userRaw);
|
|
7756
|
+
};
|
|
7757
|
+
const applyHostStyling = () => {
|
|
7758
|
+
applyHostPosition(host, hostPositionForMode(currentOptions.mode));
|
|
7759
|
+
const cachedThemeMode = persistence.loadUserPrefs().themeMode;
|
|
7760
|
+
applyHostAttributes(host, cachedThemeMode ? { ...currentOptions, themeMode: cachedThemeMode } : currentOptions);
|
|
7761
|
+
if (firstRender) {
|
|
7762
|
+
applyMode(host, resolveInitialHostMode(currentOptions));
|
|
7763
|
+
firstRender = false;
|
|
7764
|
+
}
|
|
7765
|
+
};
|
|
7766
|
+
const render2 = () => {
|
|
7767
|
+
applyHostStyling();
|
|
7768
|
+
renderPreact(
|
|
7769
|
+
h(ErrorBoundary, { onError: reportError }, h(App, { bus, hostElement: host, options: currentOptions })),
|
|
7770
|
+
appRoot
|
|
7771
|
+
);
|
|
7772
|
+
};
|
|
7773
|
+
bus.on("handshake", (response) => {
|
|
7774
|
+
if (!response.config) return;
|
|
7775
|
+
serverConfig = response.config;
|
|
7776
|
+
recompute();
|
|
7777
|
+
render2();
|
|
7778
|
+
});
|
|
7779
|
+
const untrack = createTracker(bus, {
|
|
7780
|
+
getOptions: () => currentOptions,
|
|
7781
|
+
getVisitorId: () => persistence.getVisitorId(),
|
|
7782
|
+
getConversationId: () => persistence.loadConversationId()
|
|
7783
|
+
});
|
|
7784
|
+
return {
|
|
7785
|
+
getOptions: () => currentOptions,
|
|
7786
|
+
getUserOptions: () => userRaw,
|
|
7787
|
+
render: render2,
|
|
7788
|
+
setUserOptions: (raw) => {
|
|
7789
|
+
userRaw = raw;
|
|
7790
|
+
recompute();
|
|
7791
|
+
render2();
|
|
7792
|
+
},
|
|
7793
|
+
patchUserOptions: (patch) => {
|
|
7794
|
+
userRaw = { ...userRaw, ...patch };
|
|
7795
|
+
recompute();
|
|
7796
|
+
render2();
|
|
7797
|
+
},
|
|
7798
|
+
destroy: () => untrack()
|
|
7799
|
+
};
|
|
7800
|
+
}
|
|
7801
|
+
function resolveInitialHostMode(options) {
|
|
7802
|
+
if (options.mode === "standalone") return "standalone";
|
|
7803
|
+
if (options.mode === "inline") return "inline";
|
|
7804
|
+
const persistence = createPersistence(options.widgetId, options.storage);
|
|
7805
|
+
const { panelOpen, panelSize } = resolveInitialPanelState(
|
|
7806
|
+
options,
|
|
7807
|
+
currentViewportWidth(),
|
|
7808
|
+
persistence.loadPanelOpen(),
|
|
7809
|
+
persistence.loadPanelSize()
|
|
7810
|
+
);
|
|
7811
|
+
return computeHostMode(options.mode, panelSize, panelOpen);
|
|
7812
|
+
}
|
|
7813
|
+
|
|
7670
7814
|
// src/core/hooks.ts
|
|
7671
7815
|
var log19 = logger.scope("hooks");
|
|
7672
7816
|
var HOOK_WILDCARD = "*";
|
|
@@ -7752,73 +7896,45 @@ function mount(opts) {
|
|
|
7752
7896
|
if (opts.debug !== void 0) setLogLevel(opts.debug);
|
|
7753
7897
|
const log20 = logger.scope("mount");
|
|
7754
7898
|
log20.info("mount", { widgetId: opts.widgetId, publicKey: opts.publicKey, mode: opts.presentation?.mode });
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
const widgetId = currentOptions.widgetId;
|
|
7899
|
+
const resolved = resolveOptions(opts);
|
|
7900
|
+
const widgetId = resolved.widgetId;
|
|
7758
7901
|
const existing = instances.get(widgetId);
|
|
7759
7902
|
if (existing) {
|
|
7760
7903
|
log20.debug("destroy existing instance at slot", { widgetId });
|
|
7761
7904
|
existing.destroy();
|
|
7762
7905
|
}
|
|
7763
|
-
const initialHostMode = resolveInitialHostMode(
|
|
7764
|
-
const mountResult =
|
|
7765
|
-
{ position: hostPositionForMode(
|
|
7766
|
-
|
|
7906
|
+
const initialHostMode = resolveInitialHostMode(resolved);
|
|
7907
|
+
const mountResult = resolved.iframe ? createIframeMount(
|
|
7908
|
+
{ position: hostPositionForMode(resolved.mode) },
|
|
7909
|
+
resolved.position,
|
|
7767
7910
|
initialHostMode,
|
|
7768
|
-
|
|
7911
|
+
resolved.mode,
|
|
7769
7912
|
opts.target ?? null
|
|
7770
7913
|
) : createShadowMount({
|
|
7771
|
-
position: hostPositionForMode(
|
|
7914
|
+
position: hostPositionForMode(resolved.mode),
|
|
7772
7915
|
target: opts.target ?? null
|
|
7773
7916
|
});
|
|
7774
|
-
const persistence = createPersistence(currentOptions.widgetId, currentOptions.storage);
|
|
7775
|
-
const applyResolvedHostAttributes = () => {
|
|
7776
|
-
const cachedThemeMode = persistence.loadUserPrefs().themeMode;
|
|
7777
|
-
applyHostAttributes(
|
|
7778
|
-
mountResult.hostElement,
|
|
7779
|
-
cachedThemeMode ? { ...currentOptions, themeMode: cachedThemeMode } : currentOptions
|
|
7780
|
-
);
|
|
7781
|
-
};
|
|
7782
|
-
applyResolvedHostAttributes();
|
|
7783
|
-
applyMode(mountResult.hostElement, initialHostMode);
|
|
7784
7917
|
mountResult.hostElement.dataset.widgetId = widgetId;
|
|
7785
7918
|
const bus = new EventBus();
|
|
7919
|
+
const persistence = createPersistence(widgetId, resolved.storage);
|
|
7786
7920
|
const reportError = (error) => {
|
|
7787
7921
|
bus.emit("error", error);
|
|
7788
|
-
|
|
7789
|
-
};
|
|
7790
|
-
const renderApp = () => {
|
|
7791
|
-
render(
|
|
7792
|
-
h(
|
|
7793
|
-
ErrorBoundary,
|
|
7794
|
-
{ onError: reportError },
|
|
7795
|
-
h(App, { bus, hostElement: mountResult.hostElement, options: currentOptions })
|
|
7796
|
-
),
|
|
7797
|
-
mountResult.appRoot
|
|
7798
|
-
);
|
|
7922
|
+
runtime.getOptions().onError?.(error);
|
|
7799
7923
|
};
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
renderApp();
|
|
7808
|
-
});
|
|
7809
|
-
const unsubscribeTracker = createTracker(bus, {
|
|
7810
|
-
getOptions: () => currentOptions,
|
|
7811
|
-
getVisitorId: () => persistence.getVisitorId(),
|
|
7812
|
-
getConversationId: () => persistence.loadConversationId()
|
|
7924
|
+
const runtime = createWidgetRuntime({
|
|
7925
|
+
bus,
|
|
7926
|
+
host: mountResult.hostElement,
|
|
7927
|
+
appRoot: mountResult.appRoot,
|
|
7928
|
+
persistence,
|
|
7929
|
+
initialRaw: opts,
|
|
7930
|
+
reportError
|
|
7813
7931
|
});
|
|
7932
|
+
runtime.render();
|
|
7814
7933
|
const host = mountResult.hostElement;
|
|
7815
7934
|
let unsubscribeHooks = null;
|
|
7816
7935
|
const applyUpdate = (patch) => {
|
|
7817
7936
|
if (patch.debug !== void 0) setLogLevel(patch.debug);
|
|
7818
|
-
|
|
7819
|
-
currentOptions = resolveOptions(rawOptions);
|
|
7820
|
-
applyResolvedHostAttributes();
|
|
7821
|
-
renderApp();
|
|
7937
|
+
runtime.patchUserOptions(patch);
|
|
7822
7938
|
};
|
|
7823
7939
|
const instance = {
|
|
7824
7940
|
widgetId,
|
|
@@ -7837,7 +7953,7 @@ function mount(opts) {
|
|
|
7837
7953
|
// context; App re-handshakes with the SAME visitor/conversation.
|
|
7838
7954
|
setUserContext: (userContext) => {
|
|
7839
7955
|
log20.debug("setUserContext", { keys: Object.keys(userContext) });
|
|
7840
|
-
applyUpdate({ userContext: { ...
|
|
7956
|
+
applyUpdate({ userContext: { ...runtime.getUserOptions().userContext, ...userContext } });
|
|
7841
7957
|
},
|
|
7842
7958
|
clearUserContext: () => {
|
|
7843
7959
|
log20.debug("clearUserContext");
|
|
@@ -7845,7 +7961,7 @@ function mount(opts) {
|
|
|
7845
7961
|
},
|
|
7846
7962
|
setPageContext: (pageContext) => {
|
|
7847
7963
|
log20.debug("setPageContext", { keys: Object.keys(pageContext) });
|
|
7848
|
-
applyUpdate({ pageContext: { ...
|
|
7964
|
+
applyUpdate({ pageContext: { ...runtime.getUserOptions().pageContext, ...pageContext } });
|
|
7849
7965
|
},
|
|
7850
7966
|
clearPageContext: () => {
|
|
7851
7967
|
log20.debug("clearPageContext");
|
|
@@ -7860,7 +7976,7 @@ function mount(opts) {
|
|
|
7860
7976
|
destroy: () => {
|
|
7861
7977
|
log20.info("destroy", { widgetId });
|
|
7862
7978
|
unsubscribeHooks?.();
|
|
7863
|
-
|
|
7979
|
+
runtime.destroy();
|
|
7864
7980
|
bus.clear();
|
|
7865
7981
|
render(null, mountResult.appRoot);
|
|
7866
7982
|
mountResult.destroy();
|
|
@@ -7899,18 +8015,6 @@ function hook(widgetId, event, listener) {
|
|
|
7899
8015
|
}
|
|
7900
8016
|
var version = "0.1.0";
|
|
7901
8017
|
var brand = BRAND;
|
|
7902
|
-
function resolveInitialHostMode(options) {
|
|
7903
|
-
if (options.mode === "standalone") return "standalone";
|
|
7904
|
-
if (options.mode === "inline") return "inline";
|
|
7905
|
-
const persistence = createPersistence(options.widgetId, options.storage);
|
|
7906
|
-
const { panelOpen, panelSize } = resolveInitialPanelState(
|
|
7907
|
-
options,
|
|
7908
|
-
currentViewportWidth(),
|
|
7909
|
-
persistence.loadPanelOpen(),
|
|
7910
|
-
persistence.loadPanelSize()
|
|
7911
|
-
);
|
|
7912
|
-
return computeHostMode(options.mode, panelSize, panelOpen);
|
|
7913
|
-
}
|
|
7914
8018
|
export {
|
|
7915
8019
|
HOOK_WILDCARD,
|
|
7916
8020
|
brand,
|