@helpai/elements 0.29.1 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/elements-web-component.esm.js +29 -29
- package/elements-web-component.esm.js.map +4 -4
- package/elements.cjs.js +24 -24
- package/elements.cjs.js.map +4 -4
- package/elements.esm.js +24 -24
- package/elements.esm.js.map +4 -4
- package/elements.js +24 -24
- package/elements.js.map +4 -4
- package/index.d.ts +59 -30
- package/index.mjs +600 -523
- package/package.json +1 -1
- package/schema.d.ts +49 -49
- package/web-component.d.ts +11 -5
- package/web-component.mjs +399 -325
package/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 = {
|
|
@@ -524,8 +520,9 @@ function resolveOptions(rawOpts) {
|
|
|
524
520
|
const header = opts.header ?? {};
|
|
525
521
|
const { themeMode, themeOverrides } = parseTheme(opts.theme);
|
|
526
522
|
const mode = presentation.mode ?? "floating";
|
|
527
|
-
const
|
|
528
|
-
const availableLocales = i18n.availableLocales ?? [locale];
|
|
523
|
+
const defaultLocale = resolveDefaultLocale(i18n.defaultLocale);
|
|
524
|
+
const availableLocales = i18n.availableLocales ?? [i18n.locale ?? defaultLocale];
|
|
525
|
+
const locale = clampLocale(i18n.locale ?? defaultLocale, availableLocales, defaultLocale);
|
|
529
526
|
const baseUrl = (opts.baseUrl ?? BRAND.defaultBaseUrl).replace(/\/$/, "");
|
|
530
527
|
const agentApiBaseUrl = (opts.agentApiBaseUrl ?? `${baseUrl}/api/agent`).replace(/\/$/, "");
|
|
531
528
|
const dataApiBaseUrl = (opts.dataApiBaseUrl ?? `${baseUrl}/api/data`).replace(/\/$/, "");
|
|
@@ -567,6 +564,7 @@ function resolveOptions(rawOpts) {
|
|
|
567
564
|
maxCount: opts.composer?.attachments?.maxCount ?? DEFAULT_ATTACHMENTS.maxCount
|
|
568
565
|
},
|
|
569
566
|
locale,
|
|
567
|
+
defaultLocale,
|
|
570
568
|
availableLocales,
|
|
571
569
|
startMinimized: behavior.startMinimized ?? true,
|
|
572
570
|
actions: resolveActions(mode, header.actions),
|
|
@@ -597,6 +595,11 @@ function resolveDefaultLocale(input) {
|
|
|
597
595
|
if (input && input !== "auto") return input;
|
|
598
596
|
return typeof navigator !== "undefined" && navigator.language ? navigator.language : "en";
|
|
599
597
|
}
|
|
598
|
+
function clampLocale(desired, availableLocales, defaultLocale) {
|
|
599
|
+
if (availableLocales.length <= 1 || availableLocales.includes(desired)) return desired;
|
|
600
|
+
if (availableLocales.includes(defaultLocale)) return defaultLocale;
|
|
601
|
+
return availableLocales[0] ?? desired;
|
|
602
|
+
}
|
|
600
603
|
function resolveActions(mode, overrides) {
|
|
601
604
|
return overrides ?? DEFAULT_ACTIONS_BY_MODE[mode];
|
|
602
605
|
}
|
|
@@ -745,13 +748,14 @@ function resolveLauncher(overrides) {
|
|
|
745
748
|
|
|
746
749
|
// src/core/config/overrides.ts
|
|
747
750
|
function applyOptionOverrides(options, activeLocale, activeThemeMode, activeTextSize) {
|
|
748
|
-
|
|
751
|
+
const locale = clampLocale(activeLocale, options.availableLocales, options.defaultLocale);
|
|
752
|
+
if (locale === options.locale && activeThemeMode === options.themeMode && activeTextSize === options.textSize) {
|
|
749
753
|
return options;
|
|
750
754
|
}
|
|
751
755
|
const next = { ...options };
|
|
752
|
-
if (
|
|
753
|
-
next.locale =
|
|
754
|
-
next.strings = resolveStrings(
|
|
756
|
+
if (locale !== options.locale) {
|
|
757
|
+
next.locale = locale;
|
|
758
|
+
next.strings = resolveStrings(locale, options.stringsOverride);
|
|
755
759
|
}
|
|
756
760
|
if (activeThemeMode !== options.themeMode) next.themeMode = activeThemeMode;
|
|
757
761
|
if (activeTextSize !== options.textSize) next.textSize = activeTextSize;
|
|
@@ -797,6 +801,8 @@ function mergeServerConfig(user, server) {
|
|
|
797
801
|
serverI18n ?? {},
|
|
798
802
|
userI18n
|
|
799
803
|
);
|
|
804
|
+
if (serverI18n?.availableLocales !== void 0) merged.availableLocales = serverI18n.availableLocales;
|
|
805
|
+
if (serverI18n?.defaultLocale !== void 0) merged.defaultLocale = serverI18n.defaultLocale;
|
|
800
806
|
const userStrings = userI18n?.strings;
|
|
801
807
|
const serverStrings = serverI18n?.strings;
|
|
802
808
|
if (userStrings || serverStrings) {
|
|
@@ -925,20 +931,6 @@ var REACTIVE_ATTRS = [
|
|
|
925
931
|
...SPECIAL_REACTIVE_ATTRS
|
|
926
932
|
];
|
|
927
933
|
|
|
928
|
-
// src/core/handshake-shape.ts
|
|
929
|
-
function isPlainObject(raw) {
|
|
930
|
-
return !!raw && typeof raw === "object" && !Array.isArray(raw);
|
|
931
|
-
}
|
|
932
|
-
function isSiteConfigShape(raw) {
|
|
933
|
-
return isPlainObject(raw);
|
|
934
|
-
}
|
|
935
|
-
function isBlocksConfigShape(raw) {
|
|
936
|
-
if (!isPlainObject(raw)) return false;
|
|
937
|
-
if (raw.navigation !== void 0 && !Array.isArray(raw.navigation)) return false;
|
|
938
|
-
if (raw.linkCards !== void 0 && !Array.isArray(raw.linkCards)) return false;
|
|
939
|
-
return true;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
934
|
// src/core/host-commands.ts
|
|
943
935
|
function commandEventName(cmd) {
|
|
944
936
|
return `${BRAND.cssPrefix}:${cmd}`;
|
|
@@ -959,6 +951,155 @@ function bindHostCommands(host, handlers) {
|
|
|
959
951
|
};
|
|
960
952
|
}
|
|
961
953
|
|
|
954
|
+
// src/core/ids.ts
|
|
955
|
+
function uuid7() {
|
|
956
|
+
const ts = Date.now();
|
|
957
|
+
const bytes = new Uint8Array(16);
|
|
958
|
+
bytes[0] = Math.floor(ts / 2 ** 40) & 255;
|
|
959
|
+
bytes[1] = Math.floor(ts / 2 ** 32) & 255;
|
|
960
|
+
bytes[2] = ts >>> 24 & 255;
|
|
961
|
+
bytes[3] = ts >>> 16 & 255;
|
|
962
|
+
bytes[4] = ts >>> 8 & 255;
|
|
963
|
+
bytes[5] = ts & 255;
|
|
964
|
+
fillRandom(bytes.subarray(6));
|
|
965
|
+
bytes[6] = bytes[6] & 15 | 112;
|
|
966
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
967
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
968
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
969
|
+
}
|
|
970
|
+
function fillRandom(view) {
|
|
971
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
972
|
+
crypto.getRandomValues(view);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
for (let i = 0; i < view.length; i++) view[i] = Math.floor(Math.random() * 256);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// src/core/persistence.ts
|
|
979
|
+
var log2 = logger.scope("persistence");
|
|
980
|
+
function createPersistence(widgetId, storage = defaultStorage) {
|
|
981
|
+
const prefix = `${BRAND.cssPrefix}.${widgetId}`;
|
|
982
|
+
const KEY_VISITOR = `${prefix}.visitorId`;
|
|
983
|
+
const KEY_CONVERSATION = `${prefix}.conversationId`;
|
|
984
|
+
const KEY_USER_PREFS = `${prefix}.userPrefs`;
|
|
985
|
+
const KEY_PANEL_OPEN = `${prefix}.panelOpen`;
|
|
986
|
+
const KEY_PANEL_SIZE = `${prefix}.panelSize`;
|
|
987
|
+
const KEY_CALLOUT_DISMISSED = `${prefix}.calloutDismissed`;
|
|
988
|
+
const KEY_SIDEBAR_COLLAPSED = `${prefix}.sidebarCollapsed`;
|
|
989
|
+
const KEY_ACTIVE_MODULE = `${prefix}.activeModule`;
|
|
990
|
+
const KEY_FORMS_DONE = `${prefix}.formsDone`;
|
|
991
|
+
const persistence = {
|
|
992
|
+
getVisitorId() {
|
|
993
|
+
const existing = storage.get(KEY_VISITOR);
|
|
994
|
+
if (existing) return existing;
|
|
995
|
+
const minted = uuid7();
|
|
996
|
+
storage.set(KEY_VISITOR, minted);
|
|
997
|
+
return minted;
|
|
998
|
+
},
|
|
999
|
+
setVisitorId(id) {
|
|
1000
|
+
log2.debug("setVisitorId (rebind)", { id, widgetId });
|
|
1001
|
+
storage.set(KEY_VISITOR, id);
|
|
1002
|
+
},
|
|
1003
|
+
loadConversationId() {
|
|
1004
|
+
return storage.get(KEY_CONVERSATION) ?? void 0;
|
|
1005
|
+
},
|
|
1006
|
+
saveConversationId(id) {
|
|
1007
|
+
log2.trace("saveConversationId", { id, widgetId });
|
|
1008
|
+
if (id) storage.set(KEY_CONVERSATION, id);
|
|
1009
|
+
else storage.remove(KEY_CONVERSATION);
|
|
1010
|
+
},
|
|
1011
|
+
loadUserPrefs() {
|
|
1012
|
+
const raw = storage.get(KEY_USER_PREFS);
|
|
1013
|
+
if (!raw) return {};
|
|
1014
|
+
try {
|
|
1015
|
+
const parsed = JSON.parse(raw);
|
|
1016
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
log2.warn("loadUserPrefs parse failed", { error });
|
|
1019
|
+
return {};
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
saveUserPrefs(s) {
|
|
1023
|
+
try {
|
|
1024
|
+
storage.set(KEY_USER_PREFS, JSON.stringify(s));
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
log2.warn("saveUserPrefs serialise failed", { error });
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
patchUserPrefs(patch) {
|
|
1030
|
+
const current = persistence.loadUserPrefs();
|
|
1031
|
+
persistence.saveUserPrefs({ ...current, ...patch });
|
|
1032
|
+
},
|
|
1033
|
+
loadPanelOpen() {
|
|
1034
|
+
const raw = storage.get(KEY_PANEL_OPEN);
|
|
1035
|
+
if (raw === "1") return true;
|
|
1036
|
+
if (raw === "0") return false;
|
|
1037
|
+
return null;
|
|
1038
|
+
},
|
|
1039
|
+
savePanelOpen(open) {
|
|
1040
|
+
storage.set(KEY_PANEL_OPEN, open ? "1" : "0");
|
|
1041
|
+
},
|
|
1042
|
+
loadPanelSize() {
|
|
1043
|
+
const raw = storage.get(KEY_PANEL_SIZE);
|
|
1044
|
+
return raw === "normal" || raw === "expanded" || raw === "fullscreen" ? raw : null;
|
|
1045
|
+
},
|
|
1046
|
+
savePanelSize(size) {
|
|
1047
|
+
storage.set(KEY_PANEL_SIZE, size);
|
|
1048
|
+
},
|
|
1049
|
+
loadCalloutDismissed(currentText) {
|
|
1050
|
+
if (!currentText) return false;
|
|
1051
|
+
const dismissedText = storage.get(KEY_CALLOUT_DISMISSED);
|
|
1052
|
+
return !!dismissedText && dismissedText === currentText;
|
|
1053
|
+
},
|
|
1054
|
+
saveCalloutDismissed(currentText) {
|
|
1055
|
+
if (currentText) storage.set(KEY_CALLOUT_DISMISSED, currentText);
|
|
1056
|
+
else storage.remove(KEY_CALLOUT_DISMISSED);
|
|
1057
|
+
},
|
|
1058
|
+
loadSidebarCollapsed() {
|
|
1059
|
+
const raw = storage.get(KEY_SIDEBAR_COLLAPSED);
|
|
1060
|
+
if (raw === "1") return true;
|
|
1061
|
+
if (raw === "0") return false;
|
|
1062
|
+
return null;
|
|
1063
|
+
},
|
|
1064
|
+
saveSidebarCollapsed(collapsed) {
|
|
1065
|
+
storage.set(KEY_SIDEBAR_COLLAPSED, collapsed ? "1" : "0");
|
|
1066
|
+
},
|
|
1067
|
+
loadActiveModule() {
|
|
1068
|
+
return storage.get(KEY_ACTIVE_MODULE) || null;
|
|
1069
|
+
},
|
|
1070
|
+
saveActiveModule(id) {
|
|
1071
|
+
storage.set(KEY_ACTIVE_MODULE, id);
|
|
1072
|
+
},
|
|
1073
|
+
loadFormsDone() {
|
|
1074
|
+
const raw = storage.get(KEY_FORMS_DONE);
|
|
1075
|
+
if (!raw) return {};
|
|
1076
|
+
try {
|
|
1077
|
+
const parsed = JSON.parse(raw);
|
|
1078
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
log2.warn("loadFormsDone parse failed", { error });
|
|
1081
|
+
return {};
|
|
1082
|
+
}
|
|
1083
|
+
},
|
|
1084
|
+
saveFormDone(id) {
|
|
1085
|
+
try {
|
|
1086
|
+
const done = persistence.loadFormsDone();
|
|
1087
|
+
done[id] = Date.now();
|
|
1088
|
+
storage.set(KEY_FORMS_DONE, JSON.stringify(done));
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
log2.warn("saveFormDone serialise failed", { error });
|
|
1091
|
+
}
|
|
1092
|
+
},
|
|
1093
|
+
clearConversation() {
|
|
1094
|
+
storage.remove(KEY_CONVERSATION);
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
return persistence;
|
|
1098
|
+
}
|
|
1099
|
+
function isPlainObject(v) {
|
|
1100
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
962
1103
|
// src/styles/tokens.css
|
|
963
1104
|
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';
|
|
964
1105
|
|
|
@@ -983,7 +1124,7 @@ var FONT = true ? "Inter, system-ui, -apple-system, 'Segoe UI', sans-serif" : "I
|
|
|
983
1124
|
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);
|
|
984
1125
|
|
|
985
1126
|
// src/shadow/mount.ts
|
|
986
|
-
var
|
|
1127
|
+
var log3 = logger.scope("mount");
|
|
987
1128
|
var sheetsByDocument = /* @__PURE__ */ new WeakMap();
|
|
988
1129
|
var HOST_CSS = {
|
|
989
1130
|
floating: "all: initial; position: fixed; z-index: 2147483600;",
|
|
@@ -1000,7 +1141,7 @@ function hostPositionForMode(mode) {
|
|
|
1000
1141
|
}
|
|
1001
1142
|
function createShadowMount(opts = {}) {
|
|
1002
1143
|
const position = opts.position ?? "flow";
|
|
1003
|
-
|
|
1144
|
+
log3.debug("createShadowMount", { position, hasTarget: !!opts.target, hasHost: !!opts.existingHost });
|
|
1004
1145
|
const tagName = `${BRAND.cssPrefix}-chat-host`;
|
|
1005
1146
|
if (typeof customElements !== "undefined" && !customElements.get(tagName)) {
|
|
1006
1147
|
customElements.define(tagName, class extends HTMLElement {
|
|
@@ -1084,6 +1225,123 @@ function applyHostAttributes(host, resolved) {
|
|
|
1084
1225
|
applySize(host, resolved.size);
|
|
1085
1226
|
}
|
|
1086
1227
|
|
|
1228
|
+
// src/shadow/iframe-mount.ts
|
|
1229
|
+
var log4 = logger.scope("iframe");
|
|
1230
|
+
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>';
|
|
1231
|
+
function createIframeMount(opts, position, initialMode = "closed", layoutMode = "floating", target = null) {
|
|
1232
|
+
log4.debug("createIframeMount", { position, initialMode, layoutMode, hasTarget: !!target });
|
|
1233
|
+
const iframe = document.createElement("iframe");
|
|
1234
|
+
iframe.title = `${BRAND.name} chat`;
|
|
1235
|
+
iframe.setAttribute("allow", "microphone; clipboard-write");
|
|
1236
|
+
sizeIframe(iframe, position, initialMode, layoutMode);
|
|
1237
|
+
const parent = layoutMode === "inline" && target ? target : document.body;
|
|
1238
|
+
parent.appendChild(iframe);
|
|
1239
|
+
const doc = iframe.contentDocument;
|
|
1240
|
+
if (!doc) throw new Error("iframe contentDocument unavailable (cross-origin?)");
|
|
1241
|
+
doc.open();
|
|
1242
|
+
doc.write(IFRAME_SRCDOC);
|
|
1243
|
+
doc.close();
|
|
1244
|
+
const mount2 = createShadowMount({ ...opts, target: doc.body });
|
|
1245
|
+
const mo = new MutationObserver(() => {
|
|
1246
|
+
sizeIframe(iframe, position, mount2.hostElement.dataset.mode ?? "closed", layoutMode);
|
|
1247
|
+
});
|
|
1248
|
+
mo.observe(mount2.hostElement, { attributes: true, attributeFilter: ["data-mode"] });
|
|
1249
|
+
return {
|
|
1250
|
+
...mount2,
|
|
1251
|
+
iframeElement: iframe,
|
|
1252
|
+
destroy() {
|
|
1253
|
+
mo.disconnect();
|
|
1254
|
+
mount2.destroy();
|
|
1255
|
+
iframe.remove();
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
var BASE_CSS = "border:0;background:transparent;z-index:2147483647;pointer-events:auto;";
|
|
1260
|
+
var FLOATING_SIZE_BY_MODE = {
|
|
1261
|
+
closed: { w: "88px", h: "88px" },
|
|
1262
|
+
expanded: { w: "min(calc(100vw - 32px), 680px)", h: "min(calc(100vh - 32px), 860px)" },
|
|
1263
|
+
// `open` (normal) — also the fallback for any other non-fullscreen state.
|
|
1264
|
+
open: { w: "min(calc(100vw - 32px), 440px)", h: "min(calc(100vh - 32px), 700px)" }
|
|
1265
|
+
};
|
|
1266
|
+
function sizeIframe(iframe, position, mode, layoutMode) {
|
|
1267
|
+
if (mode === "fullscreen" || layoutMode === "standalone") {
|
|
1268
|
+
iframe.style.cssText = `${BASE_CSS}position:fixed;inset:0;width:100vw;height:100vh;`;
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
if (layoutMode === "inline") {
|
|
1272
|
+
iframe.style.cssText = `${BASE_CSS}position:static;display:block;width:100%;height:100%;`;
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
const compact = typeof matchMedia === "function" && matchMedia("(max-width: 640px)").matches;
|
|
1276
|
+
if (compact && (mode === "open" || mode === "expanded")) {
|
|
1277
|
+
iframe.style.cssText = `${BASE_CSS}position:fixed;inset:0;width:100vw;height:100vh;`;
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
const vKey = position.startsWith("top-") ? "top" : "bottom";
|
|
1281
|
+
const hKey = position.endsWith("-left") ? "left" : "right";
|
|
1282
|
+
const { w, h: h2 } = FLOATING_SIZE_BY_MODE[mode] ?? FLOATING_SIZE_BY_MODE.open;
|
|
1283
|
+
iframe.style.cssText = `${BASE_CSS}position:fixed;${vKey}:16px;${hKey}:16px;width:${w};height:${h2};`;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// src/core/events.ts
|
|
1287
|
+
var EventBus = class {
|
|
1288
|
+
constructor() {
|
|
1289
|
+
__publicField(this, "handlers", /* @__PURE__ */ new Map());
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Register a listener for `name`.
|
|
1293
|
+
* @returns an `off` function that removes this listener.
|
|
1294
|
+
*/
|
|
1295
|
+
on(name, fn) {
|
|
1296
|
+
let set = this.handlers.get(name);
|
|
1297
|
+
if (!set) {
|
|
1298
|
+
set = /* @__PURE__ */ new Set();
|
|
1299
|
+
this.handlers.set(name, set);
|
|
1300
|
+
}
|
|
1301
|
+
set.add(fn);
|
|
1302
|
+
return () => this.handlers.get(name)?.delete(fn);
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Dispatch an event synchronously to all listeners.
|
|
1306
|
+
* Listener errors are swallowed — the widget must not crash on user callbacks.
|
|
1307
|
+
*/
|
|
1308
|
+
emit(name, payload) {
|
|
1309
|
+
const set = this.handlers.get(name);
|
|
1310
|
+
if (!set) return;
|
|
1311
|
+
for (const fn of set) {
|
|
1312
|
+
try {
|
|
1313
|
+
fn(payload);
|
|
1314
|
+
} catch {
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
/** Remove every listener. Called by the instance `destroy()`. */
|
|
1319
|
+
clear() {
|
|
1320
|
+
this.handlers.clear();
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
// src/core/boot.ts
|
|
1325
|
+
import { h, render as renderPreact } from "preact";
|
|
1326
|
+
|
|
1327
|
+
// src/ui/app.tsx
|
|
1328
|
+
import { useCallback as useCallback6, useEffect as useEffect17, useMemo as useMemo3, useRef as useRef9, useState as useState13 } from "preact/hooks";
|
|
1329
|
+
import { useComputed as useComputed7, useSignal as useSignal2 } from "@preact/signals";
|
|
1330
|
+
|
|
1331
|
+
// src/core/handshake-shape.ts
|
|
1332
|
+
function isPlainObject2(raw) {
|
|
1333
|
+
return !!raw && typeof raw === "object" && !Array.isArray(raw);
|
|
1334
|
+
}
|
|
1335
|
+
function isSiteConfigShape(raw) {
|
|
1336
|
+
return isPlainObject2(raw);
|
|
1337
|
+
}
|
|
1338
|
+
function isBlocksConfigShape(raw) {
|
|
1339
|
+
if (!isPlainObject2(raw)) return false;
|
|
1340
|
+
if (raw.navigation !== void 0 && !Array.isArray(raw.navigation)) return false;
|
|
1341
|
+
if (raw.linkCards !== void 0 && !Array.isArray(raw.linkCards)) return false;
|
|
1342
|
+
return true;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1087
1345
|
// src/ui/host-mode.ts
|
|
1088
1346
|
function computeHostMode(configured, panelSize, isOpen) {
|
|
1089
1347
|
if (configured === "page") return "page";
|
|
@@ -1134,30 +1392,6 @@ function createAuth(opts) {
|
|
|
1134
1392
|
};
|
|
1135
1393
|
}
|
|
1136
1394
|
|
|
1137
|
-
// src/core/ids.ts
|
|
1138
|
-
function uuid7() {
|
|
1139
|
-
const ts = Date.now();
|
|
1140
|
-
const bytes = new Uint8Array(16);
|
|
1141
|
-
bytes[0] = Math.floor(ts / 2 ** 40) & 255;
|
|
1142
|
-
bytes[1] = Math.floor(ts / 2 ** 32) & 255;
|
|
1143
|
-
bytes[2] = ts >>> 24 & 255;
|
|
1144
|
-
bytes[3] = ts >>> 16 & 255;
|
|
1145
|
-
bytes[4] = ts >>> 8 & 255;
|
|
1146
|
-
bytes[5] = ts & 255;
|
|
1147
|
-
fillRandom(bytes.subarray(6));
|
|
1148
|
-
bytes[6] = bytes[6] & 15 | 112;
|
|
1149
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
1150
|
-
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1151
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1152
|
-
}
|
|
1153
|
-
function fillRandom(view) {
|
|
1154
|
-
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
1155
|
-
crypto.getRandomValues(view);
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
for (let i = 0; i < view.length; i++) view[i] = Math.floor(Math.random() * 256);
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
1395
|
// src/stream/types.ts
|
|
1162
1396
|
var StreamError = class extends Error {
|
|
1163
1397
|
constructor(message, code, status, cause) {
|
|
@@ -1170,7 +1404,7 @@ var StreamError = class extends Error {
|
|
|
1170
1404
|
};
|
|
1171
1405
|
|
|
1172
1406
|
// src/stream/parser.ts
|
|
1173
|
-
var
|
|
1407
|
+
var log5 = logger.scope("parser");
|
|
1174
1408
|
async function* parseChatStream(response, signal7) {
|
|
1175
1409
|
if (!response.ok) {
|
|
1176
1410
|
const text = await response.text().catch(() => "");
|
|
@@ -1222,10 +1456,10 @@ ${part}` : part;
|
|
|
1222
1456
|
if (!dataPayload || dataPayload === "[DONE]") return null;
|
|
1223
1457
|
try {
|
|
1224
1458
|
const chunk = JSON.parse(dataPayload);
|
|
1225
|
-
|
|
1459
|
+
log5.trace("chunk", { type: chunk.type, eventId });
|
|
1226
1460
|
return { chunk, eventId };
|
|
1227
1461
|
} catch (err) {
|
|
1228
|
-
|
|
1462
|
+
log5.trace("frame parse failed", { err, payload: dataPayload.slice(0, 200) });
|
|
1229
1463
|
return null;
|
|
1230
1464
|
}
|
|
1231
1465
|
}
|
|
@@ -1380,7 +1614,7 @@ function messageToWireParts(m) {
|
|
|
1380
1614
|
}
|
|
1381
1615
|
|
|
1382
1616
|
// src/stream/transport.ts
|
|
1383
|
-
var
|
|
1617
|
+
var log6 = logger.scope("transport");
|
|
1384
1618
|
var MAX_RESUME_ATTEMPTS = 3;
|
|
1385
1619
|
var RESUME_BACKOFF_MS = 400;
|
|
1386
1620
|
var CONTENT_CACHE_TTL_MS = 6e4;
|
|
@@ -1422,6 +1656,12 @@ var AgentTransport = class {
|
|
|
1422
1656
|
// always sees the visitor's latest context as they navigate.
|
|
1423
1657
|
__publicField(this, "userContext");
|
|
1424
1658
|
__publicField(this, "pageContext");
|
|
1659
|
+
// Active BCP-47 locale, carried as the envelope's `locale` on EVERY request
|
|
1660
|
+
// so the backend can localize replies AND the data module can return
|
|
1661
|
+
// localized content + forms. Refreshed via `setLocale` whenever the visitor
|
|
1662
|
+
// switches (no re-handshake needed). Resolved + clamped to the deployment's
|
|
1663
|
+
// availableLocales by `resolveOptions` before it reaches here.
|
|
1664
|
+
__publicField(this, "locale");
|
|
1425
1665
|
// ---- Data API (content / forms — module-help-ai-data-api) --------------
|
|
1426
1666
|
//
|
|
1427
1667
|
// The DATA surfaces live on `dataApiBaseUrl` (default `${baseUrl}/api/data`)
|
|
@@ -1447,6 +1687,15 @@ var AgentTransport = class {
|
|
|
1447
1687
|
this.userContext = userContext;
|
|
1448
1688
|
this.pageContext = pageContext;
|
|
1449
1689
|
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Update the active locale carried on every subsequent request. Call on
|
|
1692
|
+
* mount (before the first handshake) and whenever the user picks a new
|
|
1693
|
+
* locale — the next content / forms / message request reflects it with no
|
|
1694
|
+
* extra round-trip.
|
|
1695
|
+
*/
|
|
1696
|
+
setLocale(locale) {
|
|
1697
|
+
this.locale = locale;
|
|
1698
|
+
}
|
|
1450
1699
|
/**
|
|
1451
1700
|
* Seed the visitor + conversation identity from persistence BEFORE the first
|
|
1452
1701
|
* `handshake` resolves. A mount-time `resumeStream()` runs in parallel
|
|
@@ -1463,6 +1712,7 @@ var AgentTransport = class {
|
|
|
1463
1712
|
const out = {};
|
|
1464
1713
|
if (this.visitorId) out.visitorId = this.visitorId;
|
|
1465
1714
|
if (this.conversationId) out.conversationId = this.conversationId;
|
|
1715
|
+
if (this.locale) out.locale = this.locale;
|
|
1466
1716
|
const context = encodeContext(this.userContext, this.pageContext);
|
|
1467
1717
|
if (context) out[CONTEXT_PARAM] = context;
|
|
1468
1718
|
return out;
|
|
@@ -1490,13 +1740,13 @@ var AgentTransport = class {
|
|
|
1490
1740
|
}
|
|
1491
1741
|
/** One-shot runtime bootstrap. Called once after the widget mounts. */
|
|
1492
1742
|
async handshake(body) {
|
|
1493
|
-
|
|
1743
|
+
log6.debug("handshake \u2192", {
|
|
1494
1744
|
visitorId: body.visitorId,
|
|
1495
1745
|
conversationId: body.conversationId,
|
|
1496
1746
|
locale: body.locale
|
|
1497
1747
|
});
|
|
1498
1748
|
const res = await this.postJson(DEFAULT_PATHS.handshake, body, "handshake");
|
|
1499
|
-
|
|
1749
|
+
log6.debug("handshake \u2190", {
|
|
1500
1750
|
visitorId: res.visitorId,
|
|
1501
1751
|
conversationId: res.conversationId,
|
|
1502
1752
|
hasWelcome: !!res.welcome,
|
|
@@ -1504,7 +1754,7 @@ var AgentTransport = class {
|
|
|
1504
1754
|
rebind: res.rebind
|
|
1505
1755
|
});
|
|
1506
1756
|
if (!isHandshakeResponseShape(res)) {
|
|
1507
|
-
|
|
1757
|
+
log6.warn("handshake response did not match expected shape; using raw response", { response: res });
|
|
1508
1758
|
}
|
|
1509
1759
|
this.visitorId = res.visitorId ?? body.visitorId;
|
|
1510
1760
|
this.conversationId = res.conversationId;
|
|
@@ -1512,11 +1762,11 @@ var AgentTransport = class {
|
|
|
1512
1762
|
}
|
|
1513
1763
|
/** Upload a file attachment. Returns the `{id, url}` the backend assigns. */
|
|
1514
1764
|
async uploadFile(file) {
|
|
1515
|
-
|
|
1765
|
+
log6.debug("uploadFile \u2192", { name: file.name, size: file.size, type: file.type });
|
|
1516
1766
|
const fd = new FormData();
|
|
1517
1767
|
fd.append("file", file, file.name);
|
|
1518
1768
|
const res = await this.postForm(this.uploadPath, fd, "upload");
|
|
1519
|
-
|
|
1769
|
+
log6.debug("uploadFile \u2190", { id: res.id, url: res.url });
|
|
1520
1770
|
return res;
|
|
1521
1771
|
}
|
|
1522
1772
|
/**
|
|
@@ -1524,12 +1774,12 @@ var AgentTransport = class {
|
|
|
1524
1774
|
* Only used when `features.voice === 'server'`.
|
|
1525
1775
|
*/
|
|
1526
1776
|
async transcribe(audio, mimeType = "audio/webm") {
|
|
1527
|
-
|
|
1777
|
+
log6.debug("transcribe \u2192", { size: audio.size, mimeType });
|
|
1528
1778
|
const fd = new FormData();
|
|
1529
1779
|
fd.append("audio", audio, `voice.${extensionFor(mimeType)}`);
|
|
1530
1780
|
fd.append("mimeType", mimeType);
|
|
1531
1781
|
const res = await this.postForm(this.transcribePath, fd, "transcribe");
|
|
1532
|
-
|
|
1782
|
+
log6.debug("transcribe \u2190", { textLen: res.text.length });
|
|
1533
1783
|
return res;
|
|
1534
1784
|
}
|
|
1535
1785
|
async listConversations(params = {}) {
|
|
@@ -1537,14 +1787,14 @@ var AgentTransport = class {
|
|
|
1537
1787
|
if (params.visitorId) url.searchParams.set("visitorId", params.visitorId);
|
|
1538
1788
|
if (params.limit) url.searchParams.set("limit", String(params.limit));
|
|
1539
1789
|
if (params.cursor) url.searchParams.set("cursor", params.cursor);
|
|
1540
|
-
|
|
1790
|
+
log6.debug("listConversations \u2192", {
|
|
1541
1791
|
visitorId: params.visitorId ?? this.visitorId,
|
|
1542
1792
|
limit: params.limit,
|
|
1543
1793
|
cursor: params.cursor
|
|
1544
1794
|
});
|
|
1545
1795
|
const res = await this.getJson(url.toString(), "listConversations");
|
|
1546
1796
|
const conversations = res.conversations ?? [];
|
|
1547
|
-
|
|
1797
|
+
log6.debug("listConversations \u2190", { count: conversations.length, nextCursor: res.nextCursor });
|
|
1548
1798
|
return {
|
|
1549
1799
|
conversations: conversations.map((conversation) => ({
|
|
1550
1800
|
conversationId: conversation.conversationId,
|
|
@@ -1560,10 +1810,10 @@ var AgentTransport = class {
|
|
|
1560
1810
|
async loadConversation(conversationId) {
|
|
1561
1811
|
const url = new URL(this.url(DEFAULT_PATHS.listMessages));
|
|
1562
1812
|
url.searchParams.set("conversationId", conversationId);
|
|
1563
|
-
|
|
1813
|
+
log6.debug("loadConversation \u2192", { conversationId, visitorId: this.visitorId });
|
|
1564
1814
|
const res = await this.getJson(url.toString(), "loadConversation");
|
|
1565
1815
|
const messages = res.messages ?? [];
|
|
1566
|
-
|
|
1816
|
+
log6.debug("loadConversation \u2190", {
|
|
1567
1817
|
conversationId: res.conversationId,
|
|
1568
1818
|
canContinue: res.canContinue,
|
|
1569
1819
|
messageCount: messages.length,
|
|
@@ -1585,11 +1835,11 @@ var AgentTransport = class {
|
|
|
1585
1835
|
*/
|
|
1586
1836
|
async markRead(conversationId) {
|
|
1587
1837
|
if (!this.visitorId || !conversationId) return;
|
|
1588
|
-
|
|
1838
|
+
log6.debug("markRead \u2192", { conversationId, visitorId: this.visitorId });
|
|
1589
1839
|
try {
|
|
1590
1840
|
await this.postJson(DEFAULT_PATHS.markRead, { conversationId }, "markRead");
|
|
1591
1841
|
} catch (err) {
|
|
1592
|
-
|
|
1842
|
+
log6.debug("markRead failed (non-fatal)", { err });
|
|
1593
1843
|
}
|
|
1594
1844
|
}
|
|
1595
1845
|
/** Fetch content rows by filter. `GET /pai/content` (data-API), TTL-cached. */
|
|
@@ -1597,7 +1847,7 @@ var AgentTransport = class {
|
|
|
1597
1847
|
const key = JSON.stringify(query, Object.keys(query).toSorted());
|
|
1598
1848
|
const cached = this.contentCache.get(key);
|
|
1599
1849
|
if (cached && Date.now() - cached.at < CONTENT_CACHE_TTL_MS) {
|
|
1600
|
-
|
|
1850
|
+
log6.debug("listContent \u2192 cache", query);
|
|
1601
1851
|
return cached.result;
|
|
1602
1852
|
}
|
|
1603
1853
|
const result = this.fetchContent(query).catch((err) => {
|
|
@@ -1613,10 +1863,10 @@ var AgentTransport = class {
|
|
|
1613
1863
|
if (value === void 0) continue;
|
|
1614
1864
|
url.searchParams.set(key, Array.isArray(value) ? value.join(",") : String(value));
|
|
1615
1865
|
}
|
|
1616
|
-
|
|
1866
|
+
log6.debug("listContent \u2192", query);
|
|
1617
1867
|
const res = await this.getJson(url.toString(), "listContent");
|
|
1618
1868
|
const items = res.items ?? [];
|
|
1619
|
-
|
|
1869
|
+
log6.debug("listContent \u2190", { count: items.length, total: res.pagination?.total });
|
|
1620
1870
|
return { ...res, items };
|
|
1621
1871
|
}
|
|
1622
1872
|
/**
|
|
@@ -1634,21 +1884,21 @@ var AgentTransport = class {
|
|
|
1634
1884
|
async bootstrap(conversationId) {
|
|
1635
1885
|
const url = new URL(this.dataUrl(DEFAULT_PATHS.bootstrap));
|
|
1636
1886
|
if (conversationId) url.searchParams.set("conversationId", conversationId);
|
|
1637
|
-
|
|
1887
|
+
log6.debug("bootstrap \u2192", { conversationId: conversationId ?? this.conversationId });
|
|
1638
1888
|
const res = await this.getJson(url.toString(), "bootstrap");
|
|
1639
1889
|
const forms = res.forms ?? [];
|
|
1640
1890
|
const formSubmissions = res.formSubmissions ?? [];
|
|
1641
|
-
|
|
1891
|
+
log6.debug("bootstrap \u2190", { forms: forms.length, formSubmissions: formSubmissions.length });
|
|
1642
1892
|
return { forms, formSubmissions };
|
|
1643
1893
|
}
|
|
1644
1894
|
async saveUserPrefs(userPrefs) {
|
|
1645
|
-
|
|
1895
|
+
log6.debug("saveUserPrefs \u2192", {
|
|
1646
1896
|
visitorId: this.visitorId,
|
|
1647
1897
|
conversationId: this.conversationId,
|
|
1648
1898
|
settingsKeys: Object.keys(userPrefs)
|
|
1649
1899
|
});
|
|
1650
1900
|
const res = await this.postJson(DEFAULT_PATHS.updateSettings, { userPrefs }, "saveUserPrefs");
|
|
1651
|
-
|
|
1901
|
+
log6.debug("saveUserPrefs \u2190", { ok: res.ok });
|
|
1652
1902
|
return res;
|
|
1653
1903
|
}
|
|
1654
1904
|
get uploadPath() {
|
|
@@ -1669,15 +1919,15 @@ var AgentTransport = class {
|
|
|
1669
1919
|
* that doesn't implement the endpoint) is non-fatal, so callers don't await.
|
|
1670
1920
|
*/
|
|
1671
1921
|
async submitForm(body) {
|
|
1672
|
-
|
|
1922
|
+
log6.debug("submitForm \u2192", { formId: body.formId, trigger: body.trigger, fields: Object.keys(body.values).length });
|
|
1673
1923
|
try {
|
|
1674
1924
|
await this.postJson(this.dataUrl(this.submitFormPath), body, "submitForm");
|
|
1675
1925
|
} catch (err) {
|
|
1676
|
-
|
|
1926
|
+
log6.debug("submitForm failed (non-fatal)", { err });
|
|
1677
1927
|
}
|
|
1678
1928
|
}
|
|
1679
1929
|
sendMessage(body) {
|
|
1680
|
-
|
|
1930
|
+
log6.debug("message \u2192", {
|
|
1681
1931
|
conversationId: body.conversationId,
|
|
1682
1932
|
visitorId: body.visitorId,
|
|
1683
1933
|
messageCount: body.messages.length,
|
|
@@ -1757,17 +2007,17 @@ var AgentTransport = class {
|
|
|
1757
2007
|
});
|
|
1758
2008
|
} catch (err) {
|
|
1759
2009
|
if (ctrl.signal.aborted) return;
|
|
1760
|
-
|
|
2010
|
+
log6.debug("resume fetch failed \u2014 retrying", { attempt, err });
|
|
1761
2011
|
continue;
|
|
1762
2012
|
}
|
|
1763
2013
|
if (res.status === 204 || !res.ok) {
|
|
1764
|
-
|
|
2014
|
+
log6.debug("resume \u2192 no in-flight stream", { status: res.status });
|
|
1765
2015
|
return;
|
|
1766
2016
|
}
|
|
1767
2017
|
const before = seenIds.size;
|
|
1768
2018
|
if (yield* this.drain(parseChatStream(res, ctrl.signal), seenIds, ctrl)) return;
|
|
1769
2019
|
if (seenIds.size === before) {
|
|
1770
|
-
|
|
2020
|
+
log6.debug("resume \u2192 stream closed with no new events; nothing to resume");
|
|
1771
2021
|
return;
|
|
1772
2022
|
}
|
|
1773
2023
|
}
|
|
@@ -1794,14 +2044,14 @@ var AgentTransport = class {
|
|
|
1794
2044
|
} catch (err) {
|
|
1795
2045
|
if (ctrl.signal.aborted) return true;
|
|
1796
2046
|
if (err instanceof StreamError && err.status !== void 0) throw err;
|
|
1797
|
-
|
|
2047
|
+
log6.debug("stream segment dropped", { err });
|
|
1798
2048
|
}
|
|
1799
2049
|
return false;
|
|
1800
2050
|
}
|
|
1801
2051
|
/** Abort + fire-and-forget POST to `/pai/cancel-stream`. Idempotent. */
|
|
1802
2052
|
cancelStream(ctrl, conversationId) {
|
|
1803
2053
|
if (ctrl.signal.aborted) return;
|
|
1804
|
-
|
|
2054
|
+
log6.debug("cancel stream", { conversationId, visitorId: this.visitorId });
|
|
1805
2055
|
ctrl.abort();
|
|
1806
2056
|
if (!this.visitorId || !conversationId) return;
|
|
1807
2057
|
this.fetchImpl(this.url(DEFAULT_PATHS.cancelStream), {
|
|
@@ -1810,7 +2060,7 @@ var AgentTransport = class {
|
|
|
1810
2060
|
headers: { "content-type": "application/json", ...this.opts.auth.headers() },
|
|
1811
2061
|
body: JSON.stringify(this.withEnvelope({})),
|
|
1812
2062
|
keepalive: true
|
|
1813
|
-
}).catch((err) =>
|
|
2063
|
+
}).catch((err) => log6.debug("cancel POST failed (non-fatal)", { err }));
|
|
1814
2064
|
}
|
|
1815
2065
|
// ---- Low-level fetch helpers ------------------------------------------
|
|
1816
2066
|
async *openMessageStream(body, signal7) {
|
|
@@ -1892,13 +2142,13 @@ var AgentTransport = class {
|
|
|
1892
2142
|
if (attempt >= MAX_REQUEST_RETRIES) {
|
|
1893
2143
|
throw new StreamError(`${label} failed: network error`, "network", void 0, err);
|
|
1894
2144
|
}
|
|
1895
|
-
|
|
2145
|
+
log6.debug("request network error \u2014 retrying", { label, attempt });
|
|
1896
2146
|
await sleep(backoffMs(attempt));
|
|
1897
2147
|
continue;
|
|
1898
2148
|
}
|
|
1899
2149
|
if (res.ok || !RETRYABLE_STATUS.has(res.status) || attempt >= MAX_REQUEST_RETRIES) return res;
|
|
1900
2150
|
const wait = retryAfterMs(res.headers) ?? backoffMs(attempt);
|
|
1901
|
-
|
|
2151
|
+
log6.debug("request retryable status \u2014 retrying", { label, status: res.status, attempt, wait });
|
|
1902
2152
|
await sleep(wait);
|
|
1903
2153
|
}
|
|
1904
2154
|
}
|
|
@@ -2093,7 +2343,7 @@ var TRIGGER = {
|
|
|
2093
2343
|
};
|
|
2094
2344
|
|
|
2095
2345
|
// src/stream/reducer.ts
|
|
2096
|
-
var
|
|
2346
|
+
var log7 = logger.scope("reducer");
|
|
2097
2347
|
var StreamReducer = class {
|
|
2098
2348
|
constructor(messagesSig) {
|
|
2099
2349
|
__publicField(this, "messagesSig", messagesSig);
|
|
@@ -2115,13 +2365,13 @@ var StreamReducer = class {
|
|
|
2115
2365
|
case "finish-step":
|
|
2116
2366
|
return;
|
|
2117
2367
|
case "finish":
|
|
2118
|
-
|
|
2368
|
+
log7.debug("finish", { reason: chunk.finishReason, canContinue: chunk.canContinue });
|
|
2119
2369
|
m.status = "complete";
|
|
2120
2370
|
if (chunk.finishReason) m.finishReason = chunk.finishReason;
|
|
2121
2371
|
this.bumpDone(m);
|
|
2122
2372
|
return;
|
|
2123
2373
|
case "error":
|
|
2124
|
-
|
|
2374
|
+
log7.warn("stream error chunk", { errorText: chunk.errorText });
|
|
2125
2375
|
m.status = "error";
|
|
2126
2376
|
m.errorText = chunk.errorText;
|
|
2127
2377
|
this.bumpDone(m);
|
|
@@ -2160,226 +2410,101 @@ var StreamReducer = class {
|
|
|
2160
2410
|
appendPart(m, { kind: "file", url: chunk.url, mediaType: chunk.mediaType });
|
|
2161
2411
|
return;
|
|
2162
2412
|
case "source-url": {
|
|
2163
|
-
const parts = m.partsSig.value;
|
|
2164
|
-
if (parts.some((p33) => p33.kind === "source" && p33.sourceId === chunk.sourceId)) return;
|
|
2165
|
-
appendPart(m, { kind: "source", sourceId: chunk.sourceId, url: chunk.url, title: chunk.title });
|
|
2166
|
-
return;
|
|
2167
|
-
}
|
|
2168
|
-
case "data-notification":
|
|
2169
|
-
return;
|
|
2170
|
-
default: {
|
|
2171
|
-
const _exhaustive = chunk;
|
|
2172
|
-
void _exhaustive;
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
/**
|
|
2177
|
-
* Terminal-state bump (`finish`/`error`). Touches the message's own `partsSig`
|
|
2178
|
-
* AND the list so subscribers re-render: a buffered bubble (subscribed to its
|
|
2179
|
-
* parts) flips out of the loading indicator into the finished reply, and the
|
|
2180
|
-
* list reflects the new `status`.
|
|
2181
|
-
*/
|
|
2182
|
-
bumpDone(m) {
|
|
2183
|
-
m.partsSig.value = [...m.partsSig.value];
|
|
2184
|
-
this.messagesSig.value = [...this.messagesSig.value];
|
|
2185
|
-
}
|
|
2186
|
-
};
|
|
2187
|
-
function ensureTextPart(m, kind, id) {
|
|
2188
|
-
const existing = m.partsSig.value.find((p33) => p33.kind === kind && p33.id === id);
|
|
2189
|
-
if (existing) return existing;
|
|
2190
|
-
const part = { kind, id, textSig: signal2(""), doneSig: signal2(false) };
|
|
2191
|
-
appendPart(m, part);
|
|
2192
|
-
return part;
|
|
2193
|
-
}
|
|
2194
|
-
function ensureToolPart(m, toolCallId, toolName) {
|
|
2195
|
-
const existing = m.partsSig.value.find((p33) => p33.kind === "tool" && p33.toolCallId === toolCallId);
|
|
2196
|
-
if (existing) return existing;
|
|
2197
|
-
const part = {
|
|
2198
|
-
kind: "tool",
|
|
2199
|
-
toolCallId,
|
|
2200
|
-
toolName: toolName ?? "tool",
|
|
2201
|
-
inputPartialSig: signal2(""),
|
|
2202
|
-
inputSig: signal2(void 0),
|
|
2203
|
-
outputSig: signal2(void 0),
|
|
2204
|
-
errorSig: signal2(void 0),
|
|
2205
|
-
stateSig: signal2("input"),
|
|
2206
|
-
approvalSig: signal2(void 0)
|
|
2207
|
-
};
|
|
2208
|
-
appendPart(m, part);
|
|
2209
|
-
return part;
|
|
2210
|
-
}
|
|
2211
|
-
function appendPart(m, part) {
|
|
2212
|
-
m.partsSig.value = [...m.partsSig.value, part];
|
|
2213
|
-
}
|
|
2214
|
-
function applyTool(m, chunk) {
|
|
2215
|
-
switch (chunk.type) {
|
|
2216
|
-
case "tool-input-start":
|
|
2217
|
-
ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2218
|
-
return;
|
|
2219
|
-
case "tool-input-delta": {
|
|
2220
|
-
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2221
|
-
p33.inputPartialSig.value += chunk.delta;
|
|
2222
|
-
return;
|
|
2223
|
-
}
|
|
2224
|
-
case "tool-input-available": {
|
|
2225
|
-
const p33 = ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2226
|
-
p33.inputSig.value = chunk.input;
|
|
2227
|
-
p33.stateSig.value = isAskUserInputTool(p33.toolName) ? "awaiting-input" : "awaiting";
|
|
2228
|
-
return;
|
|
2229
|
-
}
|
|
2230
|
-
case "tool-approval-request": {
|
|
2231
|
-
const p33 = ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2232
|
-
p33.approvalSig.value = { id: chunk.approvalId };
|
|
2233
|
-
p33.stateSig.value = "awaiting-approval";
|
|
2234
|
-
return;
|
|
2235
|
-
}
|
|
2236
|
-
case "tool-output-available": {
|
|
2237
|
-
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2238
|
-
p33.outputSig.value = chunk.output;
|
|
2239
|
-
p33.stateSig.value = "output";
|
|
2240
|
-
return;
|
|
2241
|
-
}
|
|
2242
|
-
case "tool-output-error": {
|
|
2243
|
-
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2244
|
-
p33.errorSig.value = chunk.errorText;
|
|
2245
|
-
p33.stateSig.value = "error";
|
|
2246
|
-
return;
|
|
2247
|
-
}
|
|
2248
|
-
default:
|
|
2249
|
-
return;
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
// src/core/persistence.ts
|
|
2254
|
-
var log6 = logger.scope("persistence");
|
|
2255
|
-
function createPersistence(widgetId, storage = defaultStorage) {
|
|
2256
|
-
const prefix = `${BRAND.cssPrefix}.${widgetId}`;
|
|
2257
|
-
const KEY_VISITOR = `${prefix}.visitorId`;
|
|
2258
|
-
const KEY_CONVERSATION = `${prefix}.conversationId`;
|
|
2259
|
-
const KEY_USER_PREFS = `${prefix}.userPrefs`;
|
|
2260
|
-
const KEY_PANEL_OPEN = `${prefix}.panelOpen`;
|
|
2261
|
-
const KEY_PANEL_SIZE = `${prefix}.panelSize`;
|
|
2262
|
-
const KEY_CALLOUT_DISMISSED = `${prefix}.calloutDismissed`;
|
|
2263
|
-
const KEY_SIDEBAR_COLLAPSED = `${prefix}.sidebarCollapsed`;
|
|
2264
|
-
const KEY_ACTIVE_MODULE = `${prefix}.activeModule`;
|
|
2265
|
-
const KEY_FORMS_DONE = `${prefix}.formsDone`;
|
|
2266
|
-
const persistence = {
|
|
2267
|
-
getVisitorId() {
|
|
2268
|
-
const existing = storage.get(KEY_VISITOR);
|
|
2269
|
-
if (existing) return existing;
|
|
2270
|
-
const minted = uuid7();
|
|
2271
|
-
storage.set(KEY_VISITOR, minted);
|
|
2272
|
-
return minted;
|
|
2273
|
-
},
|
|
2274
|
-
setVisitorId(id) {
|
|
2275
|
-
log6.debug("setVisitorId (rebind)", { id, widgetId });
|
|
2276
|
-
storage.set(KEY_VISITOR, id);
|
|
2277
|
-
},
|
|
2278
|
-
loadConversationId() {
|
|
2279
|
-
return storage.get(KEY_CONVERSATION) ?? void 0;
|
|
2280
|
-
},
|
|
2281
|
-
saveConversationId(id) {
|
|
2282
|
-
log6.trace("saveConversationId", { id, widgetId });
|
|
2283
|
-
if (id) storage.set(KEY_CONVERSATION, id);
|
|
2284
|
-
else storage.remove(KEY_CONVERSATION);
|
|
2285
|
-
},
|
|
2286
|
-
loadUserPrefs() {
|
|
2287
|
-
const raw = storage.get(KEY_USER_PREFS);
|
|
2288
|
-
if (!raw) return {};
|
|
2289
|
-
try {
|
|
2290
|
-
const parsed = JSON.parse(raw);
|
|
2291
|
-
return isPlainObject2(parsed) ? parsed : {};
|
|
2292
|
-
} catch (error) {
|
|
2293
|
-
log6.warn("loadUserPrefs parse failed", { error });
|
|
2294
|
-
return {};
|
|
2295
|
-
}
|
|
2296
|
-
},
|
|
2297
|
-
saveUserPrefs(s) {
|
|
2298
|
-
try {
|
|
2299
|
-
storage.set(KEY_USER_PREFS, JSON.stringify(s));
|
|
2300
|
-
} catch (error) {
|
|
2301
|
-
log6.warn("saveUserPrefs serialise failed", { error });
|
|
2302
|
-
}
|
|
2303
|
-
},
|
|
2304
|
-
patchUserPrefs(patch) {
|
|
2305
|
-
const current = persistence.loadUserPrefs();
|
|
2306
|
-
persistence.saveUserPrefs({ ...current, ...patch });
|
|
2307
|
-
},
|
|
2308
|
-
loadPanelOpen() {
|
|
2309
|
-
const raw = storage.get(KEY_PANEL_OPEN);
|
|
2310
|
-
if (raw === "1") return true;
|
|
2311
|
-
if (raw === "0") return false;
|
|
2312
|
-
return null;
|
|
2313
|
-
},
|
|
2314
|
-
savePanelOpen(open) {
|
|
2315
|
-
storage.set(KEY_PANEL_OPEN, open ? "1" : "0");
|
|
2316
|
-
},
|
|
2317
|
-
loadPanelSize() {
|
|
2318
|
-
const raw = storage.get(KEY_PANEL_SIZE);
|
|
2319
|
-
return raw === "normal" || raw === "expanded" || raw === "fullscreen" ? raw : null;
|
|
2320
|
-
},
|
|
2321
|
-
savePanelSize(size) {
|
|
2322
|
-
storage.set(KEY_PANEL_SIZE, size);
|
|
2323
|
-
},
|
|
2324
|
-
loadCalloutDismissed(currentText) {
|
|
2325
|
-
if (!currentText) return false;
|
|
2326
|
-
const dismissedText = storage.get(KEY_CALLOUT_DISMISSED);
|
|
2327
|
-
return !!dismissedText && dismissedText === currentText;
|
|
2328
|
-
},
|
|
2329
|
-
saveCalloutDismissed(currentText) {
|
|
2330
|
-
if (currentText) storage.set(KEY_CALLOUT_DISMISSED, currentText);
|
|
2331
|
-
else storage.remove(KEY_CALLOUT_DISMISSED);
|
|
2332
|
-
},
|
|
2333
|
-
loadSidebarCollapsed() {
|
|
2334
|
-
const raw = storage.get(KEY_SIDEBAR_COLLAPSED);
|
|
2335
|
-
if (raw === "1") return true;
|
|
2336
|
-
if (raw === "0") return false;
|
|
2337
|
-
return null;
|
|
2338
|
-
},
|
|
2339
|
-
saveSidebarCollapsed(collapsed) {
|
|
2340
|
-
storage.set(KEY_SIDEBAR_COLLAPSED, collapsed ? "1" : "0");
|
|
2341
|
-
},
|
|
2342
|
-
loadActiveModule() {
|
|
2343
|
-
return storage.get(KEY_ACTIVE_MODULE) || null;
|
|
2344
|
-
},
|
|
2345
|
-
saveActiveModule(id) {
|
|
2346
|
-
storage.set(KEY_ACTIVE_MODULE, id);
|
|
2347
|
-
},
|
|
2348
|
-
loadFormsDone() {
|
|
2349
|
-
const raw = storage.get(KEY_FORMS_DONE);
|
|
2350
|
-
if (!raw) return {};
|
|
2351
|
-
try {
|
|
2352
|
-
const parsed = JSON.parse(raw);
|
|
2353
|
-
return isPlainObject2(parsed) ? parsed : {};
|
|
2354
|
-
} catch (error) {
|
|
2355
|
-
log6.warn("loadFormsDone parse failed", { error });
|
|
2356
|
-
return {};
|
|
2413
|
+
const parts = m.partsSig.value;
|
|
2414
|
+
if (parts.some((p33) => p33.kind === "source" && p33.sourceId === chunk.sourceId)) return;
|
|
2415
|
+
appendPart(m, { kind: "source", sourceId: chunk.sourceId, url: chunk.url, title: chunk.title });
|
|
2416
|
+
return;
|
|
2357
2417
|
}
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
const
|
|
2362
|
-
|
|
2363
|
-
storage.set(KEY_FORMS_DONE, JSON.stringify(done));
|
|
2364
|
-
} catch (error) {
|
|
2365
|
-
log6.warn("saveFormDone serialise failed", { error });
|
|
2418
|
+
case "data-notification":
|
|
2419
|
+
return;
|
|
2420
|
+
default: {
|
|
2421
|
+
const _exhaustive = chunk;
|
|
2422
|
+
void _exhaustive;
|
|
2366
2423
|
}
|
|
2367
|
-
},
|
|
2368
|
-
clearConversation() {
|
|
2369
|
-
storage.remove(KEY_CONVERSATION);
|
|
2370
2424
|
}
|
|
2425
|
+
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Terminal-state bump (`finish`/`error`). Touches the message's own `partsSig`
|
|
2428
|
+
* AND the list so subscribers re-render: a buffered bubble (subscribed to its
|
|
2429
|
+
* parts) flips out of the loading indicator into the finished reply, and the
|
|
2430
|
+
* list reflects the new `status`.
|
|
2431
|
+
*/
|
|
2432
|
+
bumpDone(m) {
|
|
2433
|
+
m.partsSig.value = [...m.partsSig.value];
|
|
2434
|
+
this.messagesSig.value = [...this.messagesSig.value];
|
|
2435
|
+
}
|
|
2436
|
+
};
|
|
2437
|
+
function ensureTextPart(m, kind, id) {
|
|
2438
|
+
const existing = m.partsSig.value.find((p33) => p33.kind === kind && p33.id === id);
|
|
2439
|
+
if (existing) return existing;
|
|
2440
|
+
const part = { kind, id, textSig: signal2(""), doneSig: signal2(false) };
|
|
2441
|
+
appendPart(m, part);
|
|
2442
|
+
return part;
|
|
2443
|
+
}
|
|
2444
|
+
function ensureToolPart(m, toolCallId, toolName) {
|
|
2445
|
+
const existing = m.partsSig.value.find((p33) => p33.kind === "tool" && p33.toolCallId === toolCallId);
|
|
2446
|
+
if (existing) return existing;
|
|
2447
|
+
const part = {
|
|
2448
|
+
kind: "tool",
|
|
2449
|
+
toolCallId,
|
|
2450
|
+
toolName: toolName ?? "tool",
|
|
2451
|
+
inputPartialSig: signal2(""),
|
|
2452
|
+
inputSig: signal2(void 0),
|
|
2453
|
+
outputSig: signal2(void 0),
|
|
2454
|
+
errorSig: signal2(void 0),
|
|
2455
|
+
stateSig: signal2("input"),
|
|
2456
|
+
approvalSig: signal2(void 0)
|
|
2371
2457
|
};
|
|
2372
|
-
|
|
2458
|
+
appendPart(m, part);
|
|
2459
|
+
return part;
|
|
2373
2460
|
}
|
|
2374
|
-
function
|
|
2375
|
-
|
|
2461
|
+
function appendPart(m, part) {
|
|
2462
|
+
m.partsSig.value = [...m.partsSig.value, part];
|
|
2463
|
+
}
|
|
2464
|
+
function applyTool(m, chunk) {
|
|
2465
|
+
switch (chunk.type) {
|
|
2466
|
+
case "tool-input-start":
|
|
2467
|
+
ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2468
|
+
return;
|
|
2469
|
+
case "tool-input-delta": {
|
|
2470
|
+
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2471
|
+
p33.inputPartialSig.value += chunk.delta;
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
case "tool-input-available": {
|
|
2475
|
+
const p33 = ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2476
|
+
p33.inputSig.value = chunk.input;
|
|
2477
|
+
p33.stateSig.value = isAskUserInputTool(p33.toolName) ? "awaiting-input" : "awaiting";
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
case "tool-approval-request": {
|
|
2481
|
+
const p33 = ensureToolPart(m, chunk.toolCallId, chunk.toolName);
|
|
2482
|
+
p33.approvalSig.value = { id: chunk.approvalId };
|
|
2483
|
+
p33.stateSig.value = "awaiting-approval";
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
case "tool-output-available": {
|
|
2487
|
+
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2488
|
+
p33.outputSig.value = chunk.output;
|
|
2489
|
+
p33.stateSig.value = "output";
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
case "tool-output-error": {
|
|
2493
|
+
const p33 = ensureToolPart(m, chunk.toolCallId);
|
|
2494
|
+
p33.errorSig.value = chunk.errorText;
|
|
2495
|
+
p33.stateSig.value = "error";
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
default:
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2376
2501
|
}
|
|
2377
2502
|
|
|
2378
2503
|
// src/core/feedback/index.ts
|
|
2379
2504
|
import { signal as signal3 } from "@preact/signals";
|
|
2380
2505
|
|
|
2381
2506
|
// src/core/feedback/audio.ts
|
|
2382
|
-
var
|
|
2507
|
+
var log8 = logger.scope("feedback:audio");
|
|
2383
2508
|
var AudioEngine = class {
|
|
2384
2509
|
constructor() {
|
|
2385
2510
|
__publicField(this, "ctx", null);
|
|
@@ -2400,7 +2525,7 @@ var AudioEngine = class {
|
|
|
2400
2525
|
this.master.gain.value = 1;
|
|
2401
2526
|
this.master.connect(this.ctx.destination);
|
|
2402
2527
|
} catch (err) {
|
|
2403
|
-
|
|
2528
|
+
log8.warn("AudioContext init failed", { err });
|
|
2404
2529
|
}
|
|
2405
2530
|
}
|
|
2406
2531
|
/** Route all sounds through this multiplier. Call on volume change. */
|
|
@@ -2457,7 +2582,7 @@ var AudioEngine = class {
|
|
|
2457
2582
|
this.buffers.set(url, buf);
|
|
2458
2583
|
return buf;
|
|
2459
2584
|
}).catch((err) => {
|
|
2460
|
-
|
|
2585
|
+
log8.warn("audio fetch/decode failed", { url, err });
|
|
2461
2586
|
this.buffers.delete(url);
|
|
2462
2587
|
return null;
|
|
2463
2588
|
});
|
|
@@ -2543,7 +2668,7 @@ function acquireHaptics() {
|
|
|
2543
2668
|
}
|
|
2544
2669
|
|
|
2545
2670
|
// src/core/feedback/index.ts
|
|
2546
|
-
var
|
|
2671
|
+
var log9 = logger.scope("feedback");
|
|
2547
2672
|
var DEDUP_WINDOW_MS = 100;
|
|
2548
2673
|
var FeedbackBus = class {
|
|
2549
2674
|
constructor(sound, haptics) {
|
|
@@ -2621,7 +2746,7 @@ var FeedbackBus = class {
|
|
|
2621
2746
|
const override = this.sound.events?.[event];
|
|
2622
2747
|
if (override === false) return;
|
|
2623
2748
|
if (typeof override === "string") {
|
|
2624
|
-
this.audio.playUrl(override).catch((err) =>
|
|
2749
|
+
this.audio.playUrl(override).catch((err) => log9.warn("audio play failed", { err, url: override }));
|
|
2625
2750
|
return;
|
|
2626
2751
|
}
|
|
2627
2752
|
const tone = DEFAULT_TONES[event];
|
|
@@ -3266,7 +3391,7 @@ function AgentBadge({ agent }) {
|
|
|
3266
3391
|
import { useEffect as useEffect4, useRef as useRef2, useState as useState2 } from "preact/hooks";
|
|
3267
3392
|
|
|
3268
3393
|
// src/core/attachments.ts
|
|
3269
|
-
var
|
|
3394
|
+
var log10 = logger.scope("attachments");
|
|
3270
3395
|
function ingest(source, existing, limits) {
|
|
3271
3396
|
const incoming = collectFiles(source);
|
|
3272
3397
|
const accepted = [];
|
|
@@ -3285,7 +3410,7 @@ function ingest(source, existing, limits) {
|
|
|
3285
3410
|
totalCount += 1;
|
|
3286
3411
|
}
|
|
3287
3412
|
if (accepted.length || rejected.length) {
|
|
3288
|
-
|
|
3413
|
+
log10.debug("ingest", {
|
|
3289
3414
|
accepted: accepted.length,
|
|
3290
3415
|
rejected: rejected.map((r) => ({ name: r.file.name, reason: r.reason }))
|
|
3291
3416
|
});
|
|
@@ -3296,7 +3421,7 @@ function revoke(att) {
|
|
|
3296
3421
|
try {
|
|
3297
3422
|
URL.revokeObjectURL(att.previewUrl);
|
|
3298
3423
|
} catch (error) {
|
|
3299
|
-
|
|
3424
|
+
log10.debug("revokeObjectURL failed", { error });
|
|
3300
3425
|
}
|
|
3301
3426
|
}
|
|
3302
3427
|
function reject(file, count, bytes, limits, accept) {
|
|
@@ -3472,7 +3597,7 @@ function pickSupportedMimeType() {
|
|
|
3472
3597
|
// src/ui/composer.tsx
|
|
3473
3598
|
import { jsx as jsx6, jsxs as jsxs5 } from "preact/jsx-runtime";
|
|
3474
3599
|
var p7 = BRAND.cssPrefix;
|
|
3475
|
-
var
|
|
3600
|
+
var log11 = logger.scope("composer");
|
|
3476
3601
|
function totalBytes(items) {
|
|
3477
3602
|
return items.reduce((acc, a) => acc + a.size, 0);
|
|
3478
3603
|
}
|
|
@@ -3525,7 +3650,7 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3525
3650
|
attachFromDrop: (items) => {
|
|
3526
3651
|
const result = ingest(items, attsRef.current, options.attachments);
|
|
3527
3652
|
if (result.accepted.length === 0) return;
|
|
3528
|
-
|
|
3653
|
+
log11.info("attach (drop)", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3529
3654
|
bus.emit("attach", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3530
3655
|
setAtts((prev) => [...prev, ...result.accepted]);
|
|
3531
3656
|
}
|
|
@@ -3546,7 +3671,7 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3546
3671
|
const result = ingest(data.items, attsRef.current, options.attachments);
|
|
3547
3672
|
if (result.accepted.length > 0) {
|
|
3548
3673
|
e.preventDefault();
|
|
3549
|
-
|
|
3674
|
+
log11.info("attach (paste)", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3550
3675
|
bus.emit("attach", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3551
3676
|
setAtts((prev) => [...prev, ...result.accepted]);
|
|
3552
3677
|
return;
|
|
@@ -3580,14 +3705,14 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3580
3705
|
const input = e.target;
|
|
3581
3706
|
const result = ingest(input.files, atts, options.attachments);
|
|
3582
3707
|
if (result.accepted.length > 0) {
|
|
3583
|
-
|
|
3708
|
+
log11.info("attach (file picker)", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3584
3709
|
bus.emit("attach", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
|
|
3585
3710
|
setAtts((prev) => [...prev, ...result.accepted]);
|
|
3586
3711
|
}
|
|
3587
3712
|
input.value = "";
|
|
3588
3713
|
};
|
|
3589
3714
|
const removeAtt = (id) => {
|
|
3590
|
-
|
|
3715
|
+
log11.info("removeAttachment", { id });
|
|
3591
3716
|
bus.emit("removeAttachment", { id });
|
|
3592
3717
|
setAtts((prev) => {
|
|
3593
3718
|
const target = prev.find((a) => a.id === id);
|
|
@@ -3600,7 +3725,7 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3600
3725
|
if (!voice.isSupported) return;
|
|
3601
3726
|
if (voiceOn) {
|
|
3602
3727
|
const durationMs = voiceStartedAtRef.current ? Date.now() - voiceStartedAtRef.current : 0;
|
|
3603
|
-
|
|
3728
|
+
log11.info("voiceStop", { durationMs });
|
|
3604
3729
|
bus.emit("voiceStop", { durationMs });
|
|
3605
3730
|
voice.stop();
|
|
3606
3731
|
setVoiceOn(false);
|
|
@@ -3609,7 +3734,7 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3609
3734
|
return;
|
|
3610
3735
|
}
|
|
3611
3736
|
voiceStartedAtRef.current = Date.now();
|
|
3612
|
-
|
|
3737
|
+
log11.info("voiceStart");
|
|
3613
3738
|
bus.emit("voiceStart", void 0);
|
|
3614
3739
|
setVoiceOn(true);
|
|
3615
3740
|
feedback.play("voiceStart");
|
|
@@ -3619,7 +3744,7 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
|
|
|
3619
3744
|
setVoiceOn(false);
|
|
3620
3745
|
if (voiceStartedAtRef.current) {
|
|
3621
3746
|
const durationMs = Date.now() - voiceStartedAtRef.current;
|
|
3622
|
-
|
|
3747
|
+
log11.debug("voiceStop (auto)", { durationMs });
|
|
3623
3748
|
bus.emit("voiceStop", { durationMs });
|
|
3624
3749
|
voiceStartedAtRef.current = null;
|
|
3625
3750
|
}
|
|
@@ -5180,7 +5305,7 @@ function startOfDay(ms) {
|
|
|
5180
5305
|
|
|
5181
5306
|
// src/ui/conversation-list.tsx
|
|
5182
5307
|
import { Fragment as Fragment3, jsx as jsx18, jsxs as jsxs15 } from "preact/jsx-runtime";
|
|
5183
|
-
var
|
|
5308
|
+
var log12 = logger.scope("history");
|
|
5184
5309
|
var BUCKET_TO_STRING = {
|
|
5185
5310
|
today: "dateToday",
|
|
5186
5311
|
yesterday: "dateYesterday",
|
|
@@ -5213,7 +5338,7 @@ function ConversationList({
|
|
|
5213
5338
|
setState("loaded");
|
|
5214
5339
|
}).catch((err) => {
|
|
5215
5340
|
if (cancelled) return;
|
|
5216
|
-
|
|
5341
|
+
log12.warn("listConversations failed", { err });
|
|
5217
5342
|
setState("error");
|
|
5218
5343
|
});
|
|
5219
5344
|
return () => {
|
|
@@ -5856,7 +5981,7 @@ function ModuleState({
|
|
|
5856
5981
|
// src/ui/modules/help.tsx
|
|
5857
5982
|
import { jsx as jsx27, jsxs as jsxs23 } from "preact/jsx-runtime";
|
|
5858
5983
|
var p23 = BRAND.cssPrefix;
|
|
5859
|
-
var
|
|
5984
|
+
var log13 = logger.scope("help");
|
|
5860
5985
|
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 });
|
|
5861
5986
|
function groupByCategory(items) {
|
|
5862
5987
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -5910,7 +6035,7 @@ function HelpRoot({ transport, strings, config, nav, panelProps }) {
|
|
|
5910
6035
|
setState("loaded");
|
|
5911
6036
|
}).catch((err) => {
|
|
5912
6037
|
if (cancelled) return;
|
|
5913
|
-
|
|
6038
|
+
log13.warn("listContent (help) failed", { err });
|
|
5914
6039
|
setErrorMsg(errorMessageFor(err, strings));
|
|
5915
6040
|
setState("error");
|
|
5916
6041
|
});
|
|
@@ -5961,7 +6086,7 @@ function HomeCard({ onClick, children, testid }) {
|
|
|
5961
6086
|
// src/ui/modules/home.tsx
|
|
5962
6087
|
import { Fragment as Fragment6, jsx as jsx29, jsxs as jsxs24 } from "preact/jsx-runtime";
|
|
5963
6088
|
var p25 = BRAND.cssPrefix;
|
|
5964
|
-
var
|
|
6089
|
+
var log14 = logger.scope("home");
|
|
5965
6090
|
function resolveGreeting(props2) {
|
|
5966
6091
|
const name = props2.options.userContext?.name;
|
|
5967
6092
|
const fromConfig = props2.config.greetingText;
|
|
@@ -5980,7 +6105,7 @@ function HomeRoot(props2) {
|
|
|
5980
6105
|
useEffect12(() => {
|
|
5981
6106
|
if (!config.showRecentConversations) return;
|
|
5982
6107
|
let cancelled = false;
|
|
5983
|
-
transport.listConversations({ limit: 1 }).then((res) => !cancelled && setRecent(res.conversations?.[0] ?? null)).catch((err) => !cancelled &&
|
|
6108
|
+
transport.listConversations({ limit: 1 }).then((res) => !cancelled && setRecent(res.conversations?.[0] ?? null)).catch((err) => !cancelled && log14.warn("listConversations (home) failed", { err }));
|
|
5984
6109
|
return () => {
|
|
5985
6110
|
cancelled = true;
|
|
5986
6111
|
};
|
|
@@ -5988,7 +6113,7 @@ function HomeRoot(props2) {
|
|
|
5988
6113
|
useEffect12(() => {
|
|
5989
6114
|
if (!config.contentTags?.length) return;
|
|
5990
6115
|
let cancelled = false;
|
|
5991
|
-
transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => !cancelled && setContent(res.items ?? [])).catch((err) => !cancelled &&
|
|
6116
|
+
transport.listContent({ tags: config.contentTags, limit: 6 }).then((res) => !cancelled && setContent(res.items ?? [])).catch((err) => !cancelled && log14.warn("listContent (home) failed", { err }));
|
|
5992
6117
|
return () => {
|
|
5993
6118
|
cancelled = true;
|
|
5994
6119
|
};
|
|
@@ -6062,7 +6187,7 @@ var homeLayout = {
|
|
|
6062
6187
|
import { useEffect as useEffect13, useState as useState10 } from "preact/hooks";
|
|
6063
6188
|
import { jsx as jsx30, jsxs as jsxs25 } from "preact/jsx-runtime";
|
|
6064
6189
|
var p26 = BRAND.cssPrefix;
|
|
6065
|
-
var
|
|
6190
|
+
var log15 = logger.scope("news");
|
|
6066
6191
|
function NewsRoot({ transport, strings, config, nav, panelProps }) {
|
|
6067
6192
|
const tags = config.contentTags;
|
|
6068
6193
|
const [state, setState] = useState10("loading");
|
|
@@ -6078,7 +6203,7 @@ function NewsRoot({ transport, strings, config, nav, panelProps }) {
|
|
|
6078
6203
|
setState("loaded");
|
|
6079
6204
|
}).catch((err) => {
|
|
6080
6205
|
if (cancelled) return;
|
|
6081
|
-
|
|
6206
|
+
log15.warn("listContent (news) failed", { err });
|
|
6082
6207
|
setErrorMsg(errorMessageFor(err, strings));
|
|
6083
6208
|
setState("error");
|
|
6084
6209
|
});
|
|
@@ -6193,7 +6318,7 @@ function IframeView({ url, title, strings, onBack, actions }) {
|
|
|
6193
6318
|
import { useCallback as useCallback3, useEffect as useEffect14, useState as useState11 } from "preact/hooks";
|
|
6194
6319
|
import { jsx as jsx33, jsxs as jsxs28 } from "preact/jsx-runtime";
|
|
6195
6320
|
var p29 = BRAND.cssPrefix;
|
|
6196
|
-
var
|
|
6321
|
+
var log16 = logger.scope("content");
|
|
6197
6322
|
function ContentView({ id, title, transport, strings, onBack, actions }) {
|
|
6198
6323
|
const [item, setItem] = useState11(null);
|
|
6199
6324
|
const [failed, setFailed] = useState11(false);
|
|
@@ -6212,7 +6337,7 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
|
|
|
6212
6337
|
else setFailed(true);
|
|
6213
6338
|
}).catch((err) => {
|
|
6214
6339
|
if (cancelled) return;
|
|
6215
|
-
|
|
6340
|
+
log16.warn("listContent (reader) failed", { err });
|
|
6216
6341
|
setFailed(true);
|
|
6217
6342
|
});
|
|
6218
6343
|
return () => {
|
|
@@ -6432,7 +6557,7 @@ function useLauncherCallout({ callout, persistence }) {
|
|
|
6432
6557
|
|
|
6433
6558
|
// src/ui/app.tsx
|
|
6434
6559
|
import { jsx as jsx36, jsxs as jsxs31 } from "preact/jsx-runtime";
|
|
6435
|
-
var
|
|
6560
|
+
var log17 = logger.scope("app");
|
|
6436
6561
|
var p32 = BRAND.cssPrefix;
|
|
6437
6562
|
function App({ options, hostElement, bus }) {
|
|
6438
6563
|
const [persistence] = useState13(() => createPersistence(options.widgetId, options.storage));
|
|
@@ -6490,7 +6615,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6490
6615
|
});
|
|
6491
6616
|
const calloutPersistent = options.launcher.callout?.persistent ?? true;
|
|
6492
6617
|
const dismissCallout = useCallback6(() => {
|
|
6493
|
-
|
|
6618
|
+
log17.info("calloutDismiss");
|
|
6494
6619
|
bus.emit("calloutDismiss", void 0);
|
|
6495
6620
|
dismissCalloutRaw();
|
|
6496
6621
|
}, [bus, dismissCalloutRaw]);
|
|
@@ -6524,7 +6649,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6524
6649
|
(patch) => {
|
|
6525
6650
|
persistence.patchUserPrefs(patch);
|
|
6526
6651
|
transport.saveUserPrefs(persistence.loadUserPrefs()).catch((err) => {
|
|
6527
|
-
|
|
6652
|
+
log17.warn("saveUserPrefs failed", { err });
|
|
6528
6653
|
});
|
|
6529
6654
|
},
|
|
6530
6655
|
[persistence, transport]
|
|
@@ -6605,7 +6730,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6605
6730
|
values: rec.values
|
|
6606
6731
|
}));
|
|
6607
6732
|
} catch (err) {
|
|
6608
|
-
|
|
6733
|
+
log17.debug("bootstrap failed \u2014 no form markers (non-fatal)", { err });
|
|
6609
6734
|
return [];
|
|
6610
6735
|
}
|
|
6611
6736
|
},
|
|
@@ -6645,7 +6770,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6645
6770
|
}
|
|
6646
6771
|
} catch (err) {
|
|
6647
6772
|
if (isStale()) return;
|
|
6648
|
-
|
|
6773
|
+
log17.warn("loadThread failed; resetting conversationId", { err, conversationId });
|
|
6649
6774
|
conversationIdSig.value = void 0;
|
|
6650
6775
|
persistence.saveConversationId(void 0);
|
|
6651
6776
|
} finally {
|
|
@@ -6670,7 +6795,10 @@ function App({ options, hostElement, bus }) {
|
|
|
6670
6795
|
visitorId,
|
|
6671
6796
|
conversationId,
|
|
6672
6797
|
userPrefs: persistence.loadUserPrefs(),
|
|
6673
|
-
locale
|
|
6798
|
+
// The visitor's ACTIVE locale (persisted pick, else auto-detected) —
|
|
6799
|
+
// not the config baseline — so the handshake learns what the visitor
|
|
6800
|
+
// wants. The response's availableLocales then clamps it for the rest.
|
|
6801
|
+
locale: activeLocale
|
|
6674
6802
|
});
|
|
6675
6803
|
} catch (err) {
|
|
6676
6804
|
if (isStale()) return;
|
|
@@ -6681,7 +6809,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6681
6809
|
}
|
|
6682
6810
|
if (isStale()) return;
|
|
6683
6811
|
if (res.visitorId && res.visitorId !== visitorId) {
|
|
6684
|
-
|
|
6812
|
+
log17.info("visitor rebound", { previous: visitorId, current: res.visitorId, reason: res.rebind?.visitorId });
|
|
6685
6813
|
persistence.setVisitorId(res.visitorId);
|
|
6686
6814
|
setVisitorId(res.visitorId);
|
|
6687
6815
|
bus.emit("visitorRebound", {
|
|
@@ -6691,7 +6819,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6691
6819
|
});
|
|
6692
6820
|
}
|
|
6693
6821
|
if (res.conversationId && res.conversationId !== conversationId) {
|
|
6694
|
-
|
|
6822
|
+
log17.info("conversation rebound", {
|
|
6695
6823
|
previous: conversationId,
|
|
6696
6824
|
current: res.conversationId,
|
|
6697
6825
|
reason: res.rebind?.conversationId
|
|
@@ -6706,9 +6834,9 @@ function App({ options, hostElement, bus }) {
|
|
|
6706
6834
|
if (res.welcome?.suggestions) setSuggestions(res.welcome.suggestions);
|
|
6707
6835
|
if (options.mode === "page") {
|
|
6708
6836
|
if (res.site && isSiteConfigShape(res.site)) setParsedSite(res.site);
|
|
6709
|
-
else if (res.site)
|
|
6837
|
+
else if (res.site) log17.warn("invalid site config, using fallback", { site: res.site });
|
|
6710
6838
|
if (res.blocks && isBlocksConfigShape(res.blocks)) setParsedBlocks(res.blocks);
|
|
6711
|
-
else if (res.blocks)
|
|
6839
|
+
else if (res.blocks) log17.warn("invalid blocks config, using fallback", { blocks: res.blocks });
|
|
6712
6840
|
}
|
|
6713
6841
|
if (res.userPrefs && !newConversation) {
|
|
6714
6842
|
persistence.saveUserPrefs(res.userPrefs);
|
|
@@ -6732,7 +6860,18 @@ function App({ options, hostElement, bus }) {
|
|
|
6732
6860
|
setConversationReady(true);
|
|
6733
6861
|
}
|
|
6734
6862
|
},
|
|
6735
|
-
[
|
|
6863
|
+
[
|
|
6864
|
+
transport,
|
|
6865
|
+
visitorId,
|
|
6866
|
+
persistence,
|
|
6867
|
+
options,
|
|
6868
|
+
bus,
|
|
6869
|
+
conversationIdSig,
|
|
6870
|
+
feedback,
|
|
6871
|
+
playWelcome,
|
|
6872
|
+
loadThread,
|
|
6873
|
+
activeLocale
|
|
6874
|
+
]
|
|
6736
6875
|
);
|
|
6737
6876
|
const userSig = JSON.stringify(options.userContext ?? null);
|
|
6738
6877
|
const pageSig = JSON.stringify(options.pageContext ?? null);
|
|
@@ -6741,6 +6880,13 @@ function App({ options, hostElement, bus }) {
|
|
|
6741
6880
|
const merged = Object.keys(formContext).length ? { ...formContext, ...options.userContext } : options.userContext;
|
|
6742
6881
|
transport.setContext(merged, toWirePageContext(options.pageContext));
|
|
6743
6882
|
}, [transport, userSig, pageSig, formCtxSig]);
|
|
6883
|
+
const effectiveLocale = clampLocale(activeLocale, options.availableLocales, options.defaultLocale);
|
|
6884
|
+
useEffect17(() => {
|
|
6885
|
+
transport.setLocale(effectiveLocale);
|
|
6886
|
+
}, [transport, effectiveLocale]);
|
|
6887
|
+
useEffect17(() => {
|
|
6888
|
+
if (effectiveLocale !== activeLocale) setActiveLocale(effectiveLocale);
|
|
6889
|
+
}, [effectiveLocale, activeLocale]);
|
|
6744
6890
|
const runResume = useCallback6(
|
|
6745
6891
|
async (handle) => {
|
|
6746
6892
|
let bubble = null;
|
|
@@ -6756,7 +6902,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6756
6902
|
resumeActiveRef.current = true;
|
|
6757
6903
|
const chatTab = chatTabIdRef.current;
|
|
6758
6904
|
if (chatTab) homeNav.switchTab(chatTab);
|
|
6759
|
-
|
|
6905
|
+
log17.info("resumed in-flight reply on mount");
|
|
6760
6906
|
}
|
|
6761
6907
|
reducer.apply(evt.chunk);
|
|
6762
6908
|
if (evt.chunk.type === "finish" && evt.chunk.canContinue === false) setCanSend(false);
|
|
@@ -6772,7 +6918,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6772
6918
|
bubble.errorText = errorMessageFor(err, options.strings);
|
|
6773
6919
|
feedback.play("error");
|
|
6774
6920
|
}
|
|
6775
|
-
|
|
6921
|
+
log17.debug("mount resume ended without completing", { err });
|
|
6776
6922
|
} finally {
|
|
6777
6923
|
resumeBubbleRef.current = null;
|
|
6778
6924
|
if (bubble) {
|
|
@@ -6803,7 +6949,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6803
6949
|
const res = await dataBootRef.current;
|
|
6804
6950
|
setRemoteForms(resolveForms((res ?? { forms: [] }).forms));
|
|
6805
6951
|
} catch (err) {
|
|
6806
|
-
|
|
6952
|
+
log17.debug("bootstrap failed \u2014 treating as no forms", { err });
|
|
6807
6953
|
} finally {
|
|
6808
6954
|
setFormsReady(true);
|
|
6809
6955
|
}
|
|
@@ -6903,7 +7049,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6903
7049
|
(pt) => pt.kind === "tool" && pt.toolCallId === toolCallId
|
|
6904
7050
|
);
|
|
6905
7051
|
if (!part) {
|
|
6906
|
-
|
|
7052
|
+
log17.warn("resolveTool: tool part not found", { toolCallId });
|
|
6907
7053
|
return;
|
|
6908
7054
|
}
|
|
6909
7055
|
mutate(part);
|
|
@@ -6917,7 +7063,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6917
7063
|
);
|
|
6918
7064
|
const handleToolResult = useCallback6(
|
|
6919
7065
|
(toolCallId, output) => {
|
|
6920
|
-
|
|
7066
|
+
log17.info("toolResult", { toolCallId });
|
|
6921
7067
|
bus.emit("toolResult", { toolCallId });
|
|
6922
7068
|
resolveTool(toolCallId, TRIGGER.toolResult, (part) => {
|
|
6923
7069
|
part.outputSig.value = output;
|
|
@@ -6928,7 +7074,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6928
7074
|
);
|
|
6929
7075
|
const handleToolDecision = useCallback6(
|
|
6930
7076
|
(toolCallId, approved, reason) => {
|
|
6931
|
-
|
|
7077
|
+
log17.info("toolDecision", { toolCallId, approved });
|
|
6932
7078
|
bus.emit("toolDecision", { toolCallId, approved });
|
|
6933
7079
|
resolveTool(toolCallId, TRIGGER.toolApproval, (part) => {
|
|
6934
7080
|
const id = part.approvalSig.value?.id ?? toolCallId;
|
|
@@ -6952,7 +7098,7 @@ function App({ options, hostElement, bus }) {
|
|
|
6952
7098
|
pageArea: () => options.pageContext?.area ? String(options.pageContext.area) : void 0,
|
|
6953
7099
|
conversationId: () => conversationIdSig.value,
|
|
6954
7100
|
onComplete: (form, trigger, values, skipped) => {
|
|
6955
|
-
|
|
7101
|
+
log17.info("formSubmit", { formId: form.id, trigger, skipped });
|
|
6956
7102
|
bus.emit("formSubmit", { formId: form.id, values, skipped });
|
|
6957
7103
|
setFormMarkers((prev) => [
|
|
6958
7104
|
...prev.filter((m) => !(m.formId === form.id && m.outcome === "skipped")),
|
|
@@ -6997,12 +7143,12 @@ function App({ options, hostElement, bus }) {
|
|
|
6997
7143
|
}, [idleMs, isOpen, forms, msgCount.value]);
|
|
6998
7144
|
const handleSend = useCallback6(
|
|
6999
7145
|
async (text, attachments) => {
|
|
7000
|
-
|
|
7146
|
+
log17.info("send", { textLen: text.length, attachments: attachments.length, streaming });
|
|
7001
7147
|
if (streaming) return;
|
|
7002
7148
|
bus.emit("send", { text, attachmentCount: attachments.length });
|
|
7003
7149
|
if (suggestions.length > 0) setSuggestions([]);
|
|
7004
7150
|
const uploaded = await uploadAttachments(transport, attachments, (err) => {
|
|
7005
|
-
|
|
7151
|
+
log17.warn("upload failed", { err });
|
|
7006
7152
|
bus.emit("error", err);
|
|
7007
7153
|
options.onError?.(err);
|
|
7008
7154
|
});
|
|
@@ -7019,24 +7165,24 @@ function App({ options, hostElement, bus }) {
|
|
|
7019
7165
|
[streaming, transport, bus, options, suggestions.length, messagesSig, reducer, streamAssistant, forms]
|
|
7020
7166
|
);
|
|
7021
7167
|
const handleStop = useCallback6(() => {
|
|
7022
|
-
|
|
7168
|
+
log17.info("stop");
|
|
7023
7169
|
bus.emit("stop", void 0);
|
|
7024
7170
|
activeCancel?.();
|
|
7025
7171
|
}, [activeCancel, bus]);
|
|
7026
7172
|
const handleSoundToggle = useCallback6(() => {
|
|
7027
7173
|
feedback.toggleMuted();
|
|
7028
7174
|
const muted = feedback.mutedSig.value;
|
|
7029
|
-
|
|
7175
|
+
log17.info("soundToggle", { muted });
|
|
7030
7176
|
bus.emit("soundToggle", { muted });
|
|
7031
7177
|
}, [feedback, bus]);
|
|
7032
7178
|
const handleClose = useCallback6(() => {
|
|
7033
7179
|
if (resolveCloseAction(options.mode, panelSize) === "exit-fullscreen") {
|
|
7034
|
-
|
|
7180
|
+
log17.info("close \u2192 exit-fullscreen", { mode: options.mode });
|
|
7035
7181
|
setPanelSize("normal");
|
|
7036
7182
|
bus.emit("fullscreen", false);
|
|
7037
7183
|
return;
|
|
7038
7184
|
}
|
|
7039
|
-
|
|
7185
|
+
log17.info("close \u2192 panel closed", { mode: options.mode });
|
|
7040
7186
|
forms.fire("panel-close");
|
|
7041
7187
|
setIsOpen(false);
|
|
7042
7188
|
persistence.savePanelOpen(false);
|
|
@@ -7047,7 +7193,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7047
7193
|
if (!options.modules.list.some((m) => m.layout === "chat")) return;
|
|
7048
7194
|
transport.listConversations({ limit: 50 }).then((res) => {
|
|
7049
7195
|
unreadCountSig.value = res.conversations.reduce((sum, c) => sum + (c.unreadCount ?? 0), 0);
|
|
7050
|
-
}).catch((err) =>
|
|
7196
|
+
}).catch((err) => log17.debug("refreshUnread failed (non-fatal)", { err }));
|
|
7051
7197
|
}, [transport, options.modules, unreadCountSig]);
|
|
7052
7198
|
const unreadSeeded = useRef9(false);
|
|
7053
7199
|
useEffect17(() => {
|
|
@@ -7056,7 +7202,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7056
7202
|
refreshUnread();
|
|
7057
7203
|
}, [activated, conversationReady, refreshUnread]);
|
|
7058
7204
|
const handleOpen = useCallback6(() => {
|
|
7059
|
-
|
|
7205
|
+
log17.info("open", { mode: options.mode });
|
|
7060
7206
|
feedback.unlock();
|
|
7061
7207
|
setLauncherLeaving(true);
|
|
7062
7208
|
setIsOpen(true);
|
|
@@ -7070,11 +7216,11 @@ function App({ options, hostElement, bus }) {
|
|
|
7070
7216
|
const handleClear = useCallback6(() => {
|
|
7071
7217
|
const hasUserMessages = messagesSig.value.some((m) => m.role === "user");
|
|
7072
7218
|
if (!hasUserMessages && canSend && conversationIdSig.value) {
|
|
7073
|
-
|
|
7219
|
+
log17.info("clear \u2192 reusing empty conversation", { conversationId: conversationIdSig.value });
|
|
7074
7220
|
if (messagesSig.value.length === 0) playWelcome(welcomeRef.current?.messages);
|
|
7075
7221
|
return;
|
|
7076
7222
|
}
|
|
7077
|
-
|
|
7223
|
+
log17.info("clear \u2192 new conversation", { wasStreaming: streaming });
|
|
7078
7224
|
bus.emit("clear", void 0);
|
|
7079
7225
|
if (streaming) activeCancel?.();
|
|
7080
7226
|
messagesSig.value = [];
|
|
@@ -7092,14 +7238,14 @@ function App({ options, hostElement, bus }) {
|
|
|
7092
7238
|
}, [handleClear, homeNav, options]);
|
|
7093
7239
|
const handleExpand = useCallback6(() => {
|
|
7094
7240
|
const nextSize = panelSize === "expanded" ? "normal" : "expanded";
|
|
7095
|
-
|
|
7241
|
+
log17.info("expand", { from: panelSize, expanded: nextSize === "expanded" });
|
|
7096
7242
|
setPanelSize(nextSize);
|
|
7097
7243
|
persistence.savePanelSize(nextSize);
|
|
7098
7244
|
bus.emit("expand", nextSize === "expanded");
|
|
7099
7245
|
}, [bus, panelSize, persistence]);
|
|
7100
7246
|
const handleFullscreen = useCallback6(() => {
|
|
7101
7247
|
const nextSize = panelSize === "fullscreen" ? "normal" : "fullscreen";
|
|
7102
|
-
|
|
7248
|
+
log17.info("fullscreen", { from: panelSize, fullscreen: nextSize === "fullscreen" });
|
|
7103
7249
|
setPanelSize(nextSize);
|
|
7104
7250
|
persistence.savePanelSize(nextSize);
|
|
7105
7251
|
bus.emit("fullscreen", nextSize === "fullscreen");
|
|
@@ -7109,21 +7255,21 @@ function App({ options, hostElement, bus }) {
|
|
|
7109
7255
|
const url = new URL(options.popOutUrl);
|
|
7110
7256
|
if (conversationIdSig.value) url.searchParams.set("conversation", conversationIdSig.value);
|
|
7111
7257
|
if (options.widgetId !== "default") url.searchParams.set("widgetId", options.widgetId);
|
|
7112
|
-
|
|
7258
|
+
log17.info("popOut", { url: url.toString() });
|
|
7113
7259
|
window.open(url.toString(), "_blank", "noopener,noreferrer");
|
|
7114
7260
|
bus.emit("popOut", void 0);
|
|
7115
7261
|
}, [bus, conversationIdSig, options.popOutUrl, options.widgetId]);
|
|
7116
7262
|
const handleToggleHistory = useCallback6(() => {
|
|
7117
7263
|
setView((v) => {
|
|
7118
7264
|
const next = v === "history" ? "chat" : "history";
|
|
7119
|
-
|
|
7265
|
+
log17.info("toggleHistory", { view: next });
|
|
7120
7266
|
bus.emit("toggleHistory", { view: next });
|
|
7121
7267
|
return next;
|
|
7122
7268
|
});
|
|
7123
7269
|
}, [bus]);
|
|
7124
7270
|
const handleLocaleChange = useCallback6(
|
|
7125
7271
|
(locale) => {
|
|
7126
|
-
|
|
7272
|
+
log17.info("localeChange", { locale });
|
|
7127
7273
|
setActiveLocale(locale);
|
|
7128
7274
|
patchAndSync({ locale });
|
|
7129
7275
|
bus.emit("localeChange", locale);
|
|
@@ -7132,7 +7278,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7132
7278
|
);
|
|
7133
7279
|
const handleThemeChange = useCallback6(
|
|
7134
7280
|
(mode) => {
|
|
7135
|
-
|
|
7281
|
+
log17.info("themeChange", { mode });
|
|
7136
7282
|
setActiveThemeMode(mode);
|
|
7137
7283
|
hostElement.dataset.theme = mode;
|
|
7138
7284
|
patchAndSync({ themeMode: mode });
|
|
@@ -7142,7 +7288,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7142
7288
|
);
|
|
7143
7289
|
const handleTextSizeChange = useCallback6(
|
|
7144
7290
|
(size) => {
|
|
7145
|
-
|
|
7291
|
+
log17.info("textSizeChange", { size });
|
|
7146
7292
|
setActiveTextSize(size);
|
|
7147
7293
|
hostElement.dataset.textSize = size;
|
|
7148
7294
|
patchAndSync({ textSize: size });
|
|
@@ -7153,7 +7299,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7153
7299
|
const handleToggleSidebarCollapsed = useCallback6(() => {
|
|
7154
7300
|
setSidebarCollapsed((prev) => {
|
|
7155
7301
|
const next = !prev;
|
|
7156
|
-
|
|
7302
|
+
log17.info("sidebarToggle", { collapsed: next });
|
|
7157
7303
|
persistence.saveSidebarCollapsed(next);
|
|
7158
7304
|
bus.emit("sidebarToggle", { collapsed: next });
|
|
7159
7305
|
return next;
|
|
@@ -7161,7 +7307,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7161
7307
|
}, [persistence, bus]);
|
|
7162
7308
|
const handleWidgetSizeChange = useCallback6(
|
|
7163
7309
|
(size) => {
|
|
7164
|
-
|
|
7310
|
+
log17.info("resize", size);
|
|
7165
7311
|
patchAndSync({ widgetSize: size });
|
|
7166
7312
|
bus.emit("resize", size);
|
|
7167
7313
|
},
|
|
@@ -7169,7 +7315,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7169
7315
|
);
|
|
7170
7316
|
const handleSelectHistoryConversation = useCallback6(
|
|
7171
7317
|
async (targetConversationId) => {
|
|
7172
|
-
|
|
7318
|
+
log17.info("selectConversation", { conversationId: targetConversationId });
|
|
7173
7319
|
bus.emit("selectConversation", { conversationId: targetConversationId });
|
|
7174
7320
|
try {
|
|
7175
7321
|
const [res, markers] = await Promise.all([
|
|
@@ -7271,7 +7417,7 @@ function App({ options, hostElement, bus }) {
|
|
|
7271
7417
|
onStop: handleStop,
|
|
7272
7418
|
onSuggestion: (s) => {
|
|
7273
7419
|
const text = s.text ?? s.label;
|
|
7274
|
-
|
|
7420
|
+
log17.info("suggestion", { text });
|
|
7275
7421
|
bus.emit("suggestion", { text });
|
|
7276
7422
|
void handleSend(text, []);
|
|
7277
7423
|
},
|
|
@@ -7393,14 +7539,14 @@ function emitMessage(bus, options, role, text) {
|
|
|
7393
7539
|
|
|
7394
7540
|
// src/ui/error-boundary.tsx
|
|
7395
7541
|
import { Component } from "preact";
|
|
7396
|
-
var
|
|
7542
|
+
var log18 = logger.scope("error-boundary");
|
|
7397
7543
|
var ErrorBoundary = class extends Component {
|
|
7398
7544
|
constructor() {
|
|
7399
7545
|
super(...arguments);
|
|
7400
7546
|
__publicField(this, "state", { crashed: false });
|
|
7401
7547
|
}
|
|
7402
7548
|
componentDidCatch(error, info) {
|
|
7403
|
-
|
|
7549
|
+
log18.error("widget render crashed", { error, componentStack: info?.componentStack });
|
|
7404
7550
|
this.setState({ crashed: true });
|
|
7405
7551
|
this.props.onError(error, info?.componentStack);
|
|
7406
7552
|
}
|
|
@@ -7409,102 +7555,6 @@ var ErrorBoundary = class extends Component {
|
|
|
7409
7555
|
}
|
|
7410
7556
|
};
|
|
7411
7557
|
|
|
7412
|
-
// src/shadow/iframe-mount.ts
|
|
7413
|
-
var log18 = logger.scope("iframe");
|
|
7414
|
-
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>';
|
|
7415
|
-
function createIframeMount(opts, position, initialMode = "closed", layoutMode = "floating", target = null) {
|
|
7416
|
-
log18.debug("createIframeMount", { position, initialMode, layoutMode, hasTarget: !!target });
|
|
7417
|
-
const iframe = document.createElement("iframe");
|
|
7418
|
-
iframe.title = `${BRAND.name} chat`;
|
|
7419
|
-
iframe.setAttribute("allow", "microphone; clipboard-write");
|
|
7420
|
-
sizeIframe(iframe, position, initialMode, layoutMode);
|
|
7421
|
-
const parent = layoutMode === "inline" && target ? target : document.body;
|
|
7422
|
-
parent.appendChild(iframe);
|
|
7423
|
-
const doc = iframe.contentDocument;
|
|
7424
|
-
if (!doc) throw new Error("iframe contentDocument unavailable (cross-origin?)");
|
|
7425
|
-
doc.open();
|
|
7426
|
-
doc.write(IFRAME_SRCDOC);
|
|
7427
|
-
doc.close();
|
|
7428
|
-
const mount2 = createShadowMount({ ...opts, target: doc.body });
|
|
7429
|
-
const mo = new MutationObserver(() => {
|
|
7430
|
-
sizeIframe(iframe, position, mount2.hostElement.dataset.mode ?? "closed", layoutMode);
|
|
7431
|
-
});
|
|
7432
|
-
mo.observe(mount2.hostElement, { attributes: true, attributeFilter: ["data-mode"] });
|
|
7433
|
-
return {
|
|
7434
|
-
...mount2,
|
|
7435
|
-
iframeElement: iframe,
|
|
7436
|
-
destroy() {
|
|
7437
|
-
mo.disconnect();
|
|
7438
|
-
mount2.destroy();
|
|
7439
|
-
iframe.remove();
|
|
7440
|
-
}
|
|
7441
|
-
};
|
|
7442
|
-
}
|
|
7443
|
-
var BASE_CSS = "border:0;background:transparent;z-index:2147483647;pointer-events:auto;";
|
|
7444
|
-
var FLOATING_SIZE_BY_MODE = {
|
|
7445
|
-
closed: { w: "88px", h: "88px" },
|
|
7446
|
-
expanded: { w: "min(calc(100vw - 32px), 680px)", h: "min(calc(100vh - 32px), 860px)" },
|
|
7447
|
-
// `open` (normal) — also the fallback for any other non-fullscreen state.
|
|
7448
|
-
open: { w: "min(calc(100vw - 32px), 440px)", h: "min(calc(100vh - 32px), 700px)" }
|
|
7449
|
-
};
|
|
7450
|
-
function sizeIframe(iframe, position, mode, layoutMode) {
|
|
7451
|
-
if (mode === "fullscreen" || layoutMode === "standalone") {
|
|
7452
|
-
iframe.style.cssText = `${BASE_CSS}position:fixed;inset:0;width:100vw;height:100vh;`;
|
|
7453
|
-
return;
|
|
7454
|
-
}
|
|
7455
|
-
if (layoutMode === "inline") {
|
|
7456
|
-
iframe.style.cssText = `${BASE_CSS}position:static;display:block;width:100%;height:100%;`;
|
|
7457
|
-
return;
|
|
7458
|
-
}
|
|
7459
|
-
const compact = typeof matchMedia === "function" && matchMedia("(max-width: 640px)").matches;
|
|
7460
|
-
if (compact && (mode === "open" || mode === "expanded")) {
|
|
7461
|
-
iframe.style.cssText = `${BASE_CSS}position:fixed;inset:0;width:100vw;height:100vh;`;
|
|
7462
|
-
return;
|
|
7463
|
-
}
|
|
7464
|
-
const vKey = position.startsWith("top-") ? "top" : "bottom";
|
|
7465
|
-
const hKey = position.endsWith("-left") ? "left" : "right";
|
|
7466
|
-
const { w, h: h2 } = FLOATING_SIZE_BY_MODE[mode] ?? FLOATING_SIZE_BY_MODE.open;
|
|
7467
|
-
iframe.style.cssText = `${BASE_CSS}position:fixed;${vKey}:16px;${hKey}:16px;width:${w};height:${h2};`;
|
|
7468
|
-
}
|
|
7469
|
-
|
|
7470
|
-
// src/core/events.ts
|
|
7471
|
-
var EventBus = class {
|
|
7472
|
-
constructor() {
|
|
7473
|
-
__publicField(this, "handlers", /* @__PURE__ */ new Map());
|
|
7474
|
-
}
|
|
7475
|
-
/**
|
|
7476
|
-
* Register a listener for `name`.
|
|
7477
|
-
* @returns an `off` function that removes this listener.
|
|
7478
|
-
*/
|
|
7479
|
-
on(name, fn) {
|
|
7480
|
-
let set = this.handlers.get(name);
|
|
7481
|
-
if (!set) {
|
|
7482
|
-
set = /* @__PURE__ */ new Set();
|
|
7483
|
-
this.handlers.set(name, set);
|
|
7484
|
-
}
|
|
7485
|
-
set.add(fn);
|
|
7486
|
-
return () => this.handlers.get(name)?.delete(fn);
|
|
7487
|
-
}
|
|
7488
|
-
/**
|
|
7489
|
-
* Dispatch an event synchronously to all listeners.
|
|
7490
|
-
* Listener errors are swallowed — the widget must not crash on user callbacks.
|
|
7491
|
-
*/
|
|
7492
|
-
emit(name, payload) {
|
|
7493
|
-
const set = this.handlers.get(name);
|
|
7494
|
-
if (!set) return;
|
|
7495
|
-
for (const fn of set) {
|
|
7496
|
-
try {
|
|
7497
|
-
fn(payload);
|
|
7498
|
-
} catch {
|
|
7499
|
-
}
|
|
7500
|
-
}
|
|
7501
|
-
}
|
|
7502
|
-
/** Remove every listener. Called by the instance `destroy()`. */
|
|
7503
|
-
clear() {
|
|
7504
|
-
this.handlers.clear();
|
|
7505
|
-
}
|
|
7506
|
-
};
|
|
7507
|
-
|
|
7508
7558
|
// src/core/tracking.ts
|
|
7509
7559
|
var PROTOCOL_VERSION = "1";
|
|
7510
7560
|
var MAX_HITS_PER_MINUTE = 60;
|
|
@@ -7620,6 +7670,73 @@ function createTracker(bus, deps) {
|
|
|
7620
7670
|
};
|
|
7621
7671
|
}
|
|
7622
7672
|
|
|
7673
|
+
// src/core/boot.ts
|
|
7674
|
+
function createWidgetRuntime(params) {
|
|
7675
|
+
const { bus, host, appRoot, persistence, reportError } = params;
|
|
7676
|
+
let userRaw = params.initialRaw;
|
|
7677
|
+
let serverConfig;
|
|
7678
|
+
let currentOptions = resolveOptions(userRaw);
|
|
7679
|
+
let firstRender = true;
|
|
7680
|
+
const recompute = () => {
|
|
7681
|
+
currentOptions = resolveOptions(serverConfig ? mergeServerConfig(userRaw, serverConfig) : userRaw);
|
|
7682
|
+
};
|
|
7683
|
+
const applyHostStyling = () => {
|
|
7684
|
+
applyHostPosition(host, hostPositionForMode(currentOptions.mode));
|
|
7685
|
+
const cachedThemeMode = persistence.loadUserPrefs().themeMode;
|
|
7686
|
+
applyHostAttributes(host, cachedThemeMode ? { ...currentOptions, themeMode: cachedThemeMode } : currentOptions);
|
|
7687
|
+
if (firstRender) {
|
|
7688
|
+
applyMode(host, resolveInitialHostMode(currentOptions));
|
|
7689
|
+
firstRender = false;
|
|
7690
|
+
}
|
|
7691
|
+
};
|
|
7692
|
+
const render2 = () => {
|
|
7693
|
+
applyHostStyling();
|
|
7694
|
+
renderPreact(
|
|
7695
|
+
h(ErrorBoundary, { onError: reportError }, h(App, { bus, hostElement: host, options: currentOptions })),
|
|
7696
|
+
appRoot
|
|
7697
|
+
);
|
|
7698
|
+
};
|
|
7699
|
+
bus.on("handshake", (response) => {
|
|
7700
|
+
if (!response.config) return;
|
|
7701
|
+
serverConfig = response.config;
|
|
7702
|
+
recompute();
|
|
7703
|
+
render2();
|
|
7704
|
+
});
|
|
7705
|
+
const untrack = createTracker(bus, {
|
|
7706
|
+
getOptions: () => currentOptions,
|
|
7707
|
+
getVisitorId: () => persistence.getVisitorId(),
|
|
7708
|
+
getConversationId: () => persistence.loadConversationId()
|
|
7709
|
+
});
|
|
7710
|
+
return {
|
|
7711
|
+
getOptions: () => currentOptions,
|
|
7712
|
+
getUserOptions: () => userRaw,
|
|
7713
|
+
render: render2,
|
|
7714
|
+
setUserOptions: (raw) => {
|
|
7715
|
+
userRaw = raw;
|
|
7716
|
+
recompute();
|
|
7717
|
+
render2();
|
|
7718
|
+
},
|
|
7719
|
+
patchUserOptions: (patch) => {
|
|
7720
|
+
userRaw = { ...userRaw, ...patch };
|
|
7721
|
+
recompute();
|
|
7722
|
+
render2();
|
|
7723
|
+
},
|
|
7724
|
+
destroy: () => untrack()
|
|
7725
|
+
};
|
|
7726
|
+
}
|
|
7727
|
+
function resolveInitialHostMode(options) {
|
|
7728
|
+
if (options.mode === "standalone") return "standalone";
|
|
7729
|
+
if (options.mode === "inline") return "inline";
|
|
7730
|
+
const persistence = createPersistence(options.widgetId, options.storage);
|
|
7731
|
+
const { panelOpen, panelSize } = resolveInitialPanelState(
|
|
7732
|
+
options,
|
|
7733
|
+
currentViewportWidth(),
|
|
7734
|
+
persistence.loadPanelOpen(),
|
|
7735
|
+
persistence.loadPanelSize()
|
|
7736
|
+
);
|
|
7737
|
+
return computeHostMode(options.mode, panelSize, panelOpen);
|
|
7738
|
+
}
|
|
7739
|
+
|
|
7623
7740
|
// src/core/hooks.ts
|
|
7624
7741
|
var log19 = logger.scope("hooks");
|
|
7625
7742
|
var HOOK_WILDCARD = "*";
|
|
@@ -7705,73 +7822,45 @@ function mount(opts) {
|
|
|
7705
7822
|
if (opts.debug !== void 0) setLogLevel(opts.debug);
|
|
7706
7823
|
const log20 = logger.scope("mount");
|
|
7707
7824
|
log20.info("mount", { widgetId: opts.widgetId, publicKey: opts.publicKey, mode: opts.presentation?.mode });
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
const widgetId = currentOptions.widgetId;
|
|
7825
|
+
const resolved = resolveOptions(opts);
|
|
7826
|
+
const widgetId = resolved.widgetId;
|
|
7711
7827
|
const existing = instances.get(widgetId);
|
|
7712
7828
|
if (existing) {
|
|
7713
7829
|
log20.debug("destroy existing instance at slot", { widgetId });
|
|
7714
7830
|
existing.destroy();
|
|
7715
7831
|
}
|
|
7716
|
-
const initialHostMode = resolveInitialHostMode(
|
|
7717
|
-
const mountResult =
|
|
7718
|
-
{ position: hostPositionForMode(
|
|
7719
|
-
|
|
7832
|
+
const initialHostMode = resolveInitialHostMode(resolved);
|
|
7833
|
+
const mountResult = resolved.iframe ? createIframeMount(
|
|
7834
|
+
{ position: hostPositionForMode(resolved.mode) },
|
|
7835
|
+
resolved.position,
|
|
7720
7836
|
initialHostMode,
|
|
7721
|
-
|
|
7837
|
+
resolved.mode,
|
|
7722
7838
|
opts.target ?? null
|
|
7723
7839
|
) : createShadowMount({
|
|
7724
|
-
position: hostPositionForMode(
|
|
7840
|
+
position: hostPositionForMode(resolved.mode),
|
|
7725
7841
|
target: opts.target ?? null
|
|
7726
7842
|
});
|
|
7727
|
-
const persistence = createPersistence(currentOptions.widgetId, currentOptions.storage);
|
|
7728
|
-
const applyResolvedHostAttributes = () => {
|
|
7729
|
-
const cachedThemeMode = persistence.loadUserPrefs().themeMode;
|
|
7730
|
-
applyHostAttributes(
|
|
7731
|
-
mountResult.hostElement,
|
|
7732
|
-
cachedThemeMode ? { ...currentOptions, themeMode: cachedThemeMode } : currentOptions
|
|
7733
|
-
);
|
|
7734
|
-
};
|
|
7735
|
-
applyResolvedHostAttributes();
|
|
7736
|
-
applyMode(mountResult.hostElement, initialHostMode);
|
|
7737
7843
|
mountResult.hostElement.dataset.widgetId = widgetId;
|
|
7738
7844
|
const bus = new EventBus();
|
|
7845
|
+
const persistence = createPersistence(widgetId, resolved.storage);
|
|
7739
7846
|
const reportError = (error) => {
|
|
7740
7847
|
bus.emit("error", error);
|
|
7741
|
-
|
|
7742
|
-
};
|
|
7743
|
-
const renderApp = () => {
|
|
7744
|
-
render(
|
|
7745
|
-
h(
|
|
7746
|
-
ErrorBoundary,
|
|
7747
|
-
{ onError: reportError },
|
|
7748
|
-
h(App, { bus, hostElement: mountResult.hostElement, options: currentOptions })
|
|
7749
|
-
),
|
|
7750
|
-
mountResult.appRoot
|
|
7751
|
-
);
|
|
7848
|
+
runtime.getOptions().onError?.(error);
|
|
7752
7849
|
};
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
renderApp();
|
|
7761
|
-
});
|
|
7762
|
-
const unsubscribeTracker = createTracker(bus, {
|
|
7763
|
-
getOptions: () => currentOptions,
|
|
7764
|
-
getVisitorId: () => persistence.getVisitorId(),
|
|
7765
|
-
getConversationId: () => persistence.loadConversationId()
|
|
7850
|
+
const runtime = createWidgetRuntime({
|
|
7851
|
+
bus,
|
|
7852
|
+
host: mountResult.hostElement,
|
|
7853
|
+
appRoot: mountResult.appRoot,
|
|
7854
|
+
persistence,
|
|
7855
|
+
initialRaw: opts,
|
|
7856
|
+
reportError
|
|
7766
7857
|
});
|
|
7858
|
+
runtime.render();
|
|
7767
7859
|
const host = mountResult.hostElement;
|
|
7768
7860
|
let unsubscribeHooks = null;
|
|
7769
7861
|
const applyUpdate = (patch) => {
|
|
7770
7862
|
if (patch.debug !== void 0) setLogLevel(patch.debug);
|
|
7771
|
-
|
|
7772
|
-
currentOptions = resolveOptions(rawOptions);
|
|
7773
|
-
applyResolvedHostAttributes();
|
|
7774
|
-
renderApp();
|
|
7863
|
+
runtime.patchUserOptions(patch);
|
|
7775
7864
|
};
|
|
7776
7865
|
const instance = {
|
|
7777
7866
|
widgetId,
|
|
@@ -7790,7 +7879,7 @@ function mount(opts) {
|
|
|
7790
7879
|
// context; App re-handshakes with the SAME visitor/conversation.
|
|
7791
7880
|
setUserContext: (userContext) => {
|
|
7792
7881
|
log20.debug("setUserContext", { keys: Object.keys(userContext) });
|
|
7793
|
-
applyUpdate({ userContext: { ...
|
|
7882
|
+
applyUpdate({ userContext: { ...runtime.getUserOptions().userContext, ...userContext } });
|
|
7794
7883
|
},
|
|
7795
7884
|
clearUserContext: () => {
|
|
7796
7885
|
log20.debug("clearUserContext");
|
|
@@ -7798,7 +7887,7 @@ function mount(opts) {
|
|
|
7798
7887
|
},
|
|
7799
7888
|
setPageContext: (pageContext) => {
|
|
7800
7889
|
log20.debug("setPageContext", { keys: Object.keys(pageContext) });
|
|
7801
|
-
applyUpdate({ pageContext: { ...
|
|
7890
|
+
applyUpdate({ pageContext: { ...runtime.getUserOptions().pageContext, ...pageContext } });
|
|
7802
7891
|
},
|
|
7803
7892
|
clearPageContext: () => {
|
|
7804
7893
|
log20.debug("clearPageContext");
|
|
@@ -7813,7 +7902,7 @@ function mount(opts) {
|
|
|
7813
7902
|
destroy: () => {
|
|
7814
7903
|
log20.info("destroy", { widgetId });
|
|
7815
7904
|
unsubscribeHooks?.();
|
|
7816
|
-
|
|
7905
|
+
runtime.destroy();
|
|
7817
7906
|
bus.clear();
|
|
7818
7907
|
render(null, mountResult.appRoot);
|
|
7819
7908
|
mountResult.destroy();
|
|
@@ -7852,18 +7941,6 @@ function hook(widgetId, event, listener) {
|
|
|
7852
7941
|
}
|
|
7853
7942
|
var version = "0.1.0";
|
|
7854
7943
|
var brand = BRAND;
|
|
7855
|
-
function resolveInitialHostMode(options) {
|
|
7856
|
-
if (options.mode === "standalone") return "standalone";
|
|
7857
|
-
if (options.mode === "inline") return "inline";
|
|
7858
|
-
const persistence = createPersistence(options.widgetId, options.storage);
|
|
7859
|
-
const { panelOpen, panelSize } = resolveInitialPanelState(
|
|
7860
|
-
options,
|
|
7861
|
-
currentViewportWidth(),
|
|
7862
|
-
persistence.loadPanelOpen(),
|
|
7863
|
-
persistence.loadPanelSize()
|
|
7864
|
-
);
|
|
7865
|
-
return computeHostMode(options.mode, panelSize, panelOpen);
|
|
7866
|
-
}
|
|
7867
7944
|
export {
|
|
7868
7945
|
HOOK_WILDCARD,
|
|
7869
7946
|
brand,
|