@helpai/elements 0.29.0 → 0.29.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/index.mjs CHANGED
@@ -6225,7 +6225,7 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6225
6225
  return /* @__PURE__ */ jsxs28("article", { class: `${p29}-content`, "data-testid": TID.contentView, children: [
6226
6226
  item.image ? /* @__PURE__ */ jsx33("img", { class: `${p29}-content-hero`, src: item.image, alt: "", loading: "lazy" }) : null,
6227
6227
  item.description ? /* @__PURE__ */ jsx33("p", { class: `${p29}-content-subtitle`, children: item.description }) : null,
6228
- item.tags && item.tags.length > 0 ? /* @__PURE__ */ jsx33("span", { class: `${p29}-news-tags`, children: item.tags.map((t) => /* @__PURE__ */ jsx33("span", { class: `${p29}-news-tag`, children: t }, t)) }) : null,
6228
+ item.tags?.length ? /* @__PURE__ */ jsx33("span", { class: `${p29}-news-tags`, children: item.tags.map((t) => /* @__PURE__ */ jsx33("span", { class: `${p29}-news-tag`, children: t }, t)) }) : null,
6229
6229
  /* @__PURE__ */ jsx33(StaticMarkdown, { text: item.content ?? "" })
6230
6230
  ] });
6231
6231
  }
@@ -7606,7 +7606,9 @@ function createTracker(bus, deps) {
7606
7606
  if (url.length > MAX_URL_LENGTH) return;
7607
7607
  const img = new Image(1, 1);
7608
7608
  inflight.add(img);
7609
- img.onload = img.onerror = () => inflight.delete(img);
7609
+ const settle = () => inflight.delete(img);
7610
+ img.addEventListener("load", settle, { once: true });
7611
+ img.addEventListener("error", settle, { once: true });
7610
7612
  img.src = url;
7611
7613
  };
7612
7614
  const offs = Object.keys(TRACKED).map(
package/package.json CHANGED
@@ -80,5 +80,5 @@
80
80
  ],
81
81
  "type": "module",
82
82
  "types": "./index.d.ts",
83
- "version": "0.29.0"
83
+ "version": "0.29.1"
84
84
  }
package/web-component.mjs CHANGED
@@ -6280,7 +6280,7 @@ function ContentView({ id, title, transport, strings, onBack, actions }) {
6280
6280
  return /* @__PURE__ */ jsxs28("article", { class: `${p29}-content`, "data-testid": TID.contentView, children: [
6281
6281
  item.image ? /* @__PURE__ */ jsx33("img", { class: `${p29}-content-hero`, src: item.image, alt: "", loading: "lazy" }) : null,
6282
6282
  item.description ? /* @__PURE__ */ jsx33("p", { class: `${p29}-content-subtitle`, children: item.description }) : null,
6283
- item.tags && item.tags.length > 0 ? /* @__PURE__ */ jsx33("span", { class: `${p29}-news-tags`, children: item.tags.map((t) => /* @__PURE__ */ jsx33("span", { class: `${p29}-news-tag`, children: t }, t)) }) : null,
6283
+ item.tags?.length ? /* @__PURE__ */ jsx33("span", { class: `${p29}-news-tags`, children: item.tags.map((t) => /* @__PURE__ */ jsx33("span", { class: `${p29}-news-tag`, children: t }, t)) }) : null,
6284
6284
  /* @__PURE__ */ jsx33(StaticMarkdown, { text: item.content ?? "" })
6285
6285
  ] });
6286
6286
  }
@@ -7494,6 +7494,121 @@ var EventBus = class {
7494
7494
  }
7495
7495
  };
7496
7496
 
7497
+ // src/core/tracking.ts
7498
+ var PROTOCOL_VERSION = "1";
7499
+ var MAX_HITS_PER_MINUTE = 60;
7500
+ var MAX_URL_LENGTH = 2e3;
7501
+ var TRACKED = {
7502
+ // Panel lifecycle — the owner's "is the widget used at all" funnel.
7503
+ open: () => void 0,
7504
+ close: () => void 0,
7505
+ expand: (on) => ({ on }),
7506
+ fullscreen: (on) => ({ on }),
7507
+ popOut: () => void 0,
7508
+ // Conversation funnel. `send` is the key conversion; text never rides.
7509
+ send: (p33) => ({ attachments: p33.attachmentCount }),
7510
+ message: (p33) => ({ role: p33.role }),
7511
+ stop: () => void 0,
7512
+ clear: () => void 0,
7513
+ suggestion: () => void 0,
7514
+ toggleHistory: (p33) => ({ view: p33.view }),
7515
+ handshake: () => void 0,
7516
+ // Forms + human-in-the-loop — ids and outcomes, never values.
7517
+ formSubmit: (p33) => ({ formId: p33.formId, skipped: p33.skipped }),
7518
+ toolResult: () => void 0,
7519
+ toolDecision: (p33) => ({ approved: p33.approved }),
7520
+ // Composer / attachments / voice.
7521
+ attach: (p33) => ({ count: p33.count, bytes: p33.totalBytes }),
7522
+ voiceStart: () => void 0,
7523
+ voiceStop: (p33) => ({ ms: p33.durationMs }),
7524
+ voiceCancel: () => void 0,
7525
+ // Preferences — how visitors tune the surface.
7526
+ localeChange: (locale) => ({ locale }),
7527
+ themeChange: (theme) => ({ theme }),
7528
+ textSizeChange: (size) => ({ size }),
7529
+ soundToggle: (p33) => ({ muted: p33.muted }),
7530
+ sidebarToggle: (p33) => ({ collapsed: p33.collapsed }),
7531
+ calloutDismiss: () => void 0,
7532
+ // Health signal only — the error object itself never leaves the page.
7533
+ error: () => void 0
7534
+ };
7535
+ function privacySignalsOff() {
7536
+ const nav = navigator;
7537
+ return nav.globalPrivacyControl === true || nav.doNotTrack === "1";
7538
+ }
7539
+ function pageUrl() {
7540
+ return location.origin + location.pathname;
7541
+ }
7542
+ function referrerOrigin() {
7543
+ try {
7544
+ return document.referrer ? new URL(document.referrer).origin : "";
7545
+ } catch {
7546
+ return "";
7547
+ }
7548
+ }
7549
+ function createTracker(bus, deps) {
7550
+ const sampleRoll = Math.random();
7551
+ let windowStart = 0;
7552
+ let windowHits = 0;
7553
+ const inflight = /* @__PURE__ */ new Set();
7554
+ const hit = (event, dims) => {
7555
+ const { tracking, publicKey, aiAgentDeploymentId, mode, themeMode } = deps.getOptions();
7556
+ if (!tracking.enabled || privacySignalsOff()) return;
7557
+ if (sampleRoll >= tracking.sampleRate) return;
7558
+ const now = performance.now();
7559
+ if (now - windowStart > 6e4) {
7560
+ windowStart = now;
7561
+ windowHits = 0;
7562
+ }
7563
+ if (++windowHits > MAX_HITS_PER_MINUTE) return;
7564
+ const params = new URLSearchParams();
7565
+ params.set("v", PROTOCOL_VERSION);
7566
+ params.set("e", event);
7567
+ if (tracking.token) params.set("tk", tracking.token);
7568
+ if (publicKey) params.set("pk", publicKey);
7569
+ if (aiAgentDeploymentId) params.set("dep", aiAgentDeploymentId);
7570
+ params.set("vid", deps.getVisitorId());
7571
+ const cid = deps.getConversationId();
7572
+ if (cid) params.set("cid", cid);
7573
+ params.set("url", pageUrl());
7574
+ const ref = referrerOrigin();
7575
+ if (ref) params.set("ref", ref);
7576
+ params.set("sw", String(screen.width));
7577
+ params.set("sh", String(screen.height));
7578
+ params.set("vw", String(innerWidth));
7579
+ params.set("vh", String(innerHeight));
7580
+ params.set("dpr", String(devicePixelRatio));
7581
+ params.set("lang", navigator.language);
7582
+ try {
7583
+ params.set("tz", Intl.DateTimeFormat().resolvedOptions().timeZone);
7584
+ } catch {
7585
+ }
7586
+ params.set("m", mode);
7587
+ params.set("th", themeMode);
7588
+ if (dims) {
7589
+ for (const [k, val] of Object.entries(dims)) {
7590
+ if (val !== void 0) params.set(k, String(val));
7591
+ }
7592
+ }
7593
+ params.set("ts", String(Date.now()));
7594
+ const url = `${tracking.endpoint}?${params}`;
7595
+ if (url.length > MAX_URL_LENGTH) return;
7596
+ const img = new Image(1, 1);
7597
+ inflight.add(img);
7598
+ const settle = () => inflight.delete(img);
7599
+ img.addEventListener("load", settle, { once: true });
7600
+ img.addEventListener("error", settle, { once: true });
7601
+ img.src = url;
7602
+ };
7603
+ const offs = Object.keys(TRACKED).map(
7604
+ (event) => bus.on(event, (payload) => hit(event, TRACKED[event]?.(payload)))
7605
+ );
7606
+ return () => {
7607
+ for (const off of offs) off();
7608
+ inflight.clear();
7609
+ };
7610
+ }
7611
+
7497
7612
  // src/core/hooks.ts
7498
7613
  var log19 = logger.scope("hooks");
7499
7614
 
@@ -7527,6 +7642,19 @@ var ChatElement = class extends HTMLElement {
7527
7642
  * same user-wins-per-field merge policy.
7528
7643
  */
7529
7644
  __publicField(this, "rawOptions", {});
7645
+ /**
7646
+ * Latest resolved options — refreshed every `renderApp()` so the usage
7647
+ * tracker (created once in `boot`) reads live, post-handshake-merge
7648
+ * values (`tracking.enabled` / `endpoint` / `token`).
7649
+ *
7650
+ * This path is independent of `mount()` (it supports N elements per page
7651
+ * without a shared `widgetId` registry), so anything `mount()` wires onto
7652
+ * the bus must be wired here too — the tracker is the current example;
7653
+ * keep them in lockstep.
7654
+ */
7655
+ __publicField(this, "resolved", null);
7656
+ /** Tracker unsubscribe — released on disconnect. */
7657
+ __publicField(this, "untrack", null);
7530
7658
  }
7531
7659
  static get observedAttributes() {
7532
7660
  return REACTIVE_ATTRS;
@@ -7536,6 +7664,8 @@ var ChatElement = class extends HTMLElement {
7536
7664
  }
7537
7665
  disconnectedCallback() {
7538
7666
  if (!this.mountResult) return;
7667
+ this.untrack?.();
7668
+ this.untrack = null;
7539
7669
  render2(null, this.mountResult.appRoot);
7540
7670
  this.bus.clear();
7541
7671
  }
@@ -7555,6 +7685,12 @@ var ChatElement = class extends HTMLElement {
7555
7685
  this.rawOptions = mergeServerConfig(this.rawOptions, response.config);
7556
7686
  this.renderApp();
7557
7687
  });
7688
+ const persistence = createPersistence(resolved.widgetId, resolved.storage);
7689
+ this.untrack = createTracker(this.bus, {
7690
+ getOptions: () => this.resolved ?? resolved,
7691
+ getVisitorId: () => persistence.getVisitorId(),
7692
+ getConversationId: () => persistence.loadConversationId()
7693
+ });
7558
7694
  }
7559
7695
  this.renderApp();
7560
7696
  }
@@ -7565,6 +7701,7 @@ var ChatElement = class extends HTMLElement {
7565
7701
  renderApp() {
7566
7702
  if (!this.mountResult) return;
7567
7703
  const resolved = resolveOptions(this.rawOptions);
7704
+ this.resolved = resolved;
7568
7705
  const host = this.mountResult.hostElement;
7569
7706
  applyHostPosition(host, hostPositionForMode(resolved.mode));
7570
7707
  const cachedThemeMode = createPersistence(resolved.widgetId, resolved.storage).loadUserPrefs().themeMode;