@smooai/chat-widget 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11842,7 +11842,7 @@ var SmoothAgentChat = (function(exports) {
11842
11842
  }) : e[r] = t, e;
11843
11843
  }
11844
11844
  //#endregion
11845
- //#region node_modules/.pnpm/@smooai+smooth-operator@1.8.0/node_modules/@smooai/smooth-operator/dist/transport.js
11845
+ //#region node_modules/.pnpm/@smooai+smooth-operator@1.21.1/node_modules/@smooai/smooth-operator/dist/transport.js
11846
11846
  /**
11847
11847
  * Transport abstraction for the client.
11848
11848
  *
@@ -11967,7 +11967,7 @@ var SmoothAgentChat = (function(exports) {
11967
11967
  }
11968
11968
  };
11969
11969
  //#endregion
11970
- //#region node_modules/.pnpm/@smooai+smooth-operator@1.8.0/node_modules/@smooai/smooth-operator/dist/types.js
11970
+ //#region node_modules/.pnpm/@smooai+smooth-operator@1.21.1/node_modules/@smooai/smooth-operator/dist/types.js
11971
11971
  /** Every server→client `type` discriminator value. */
11972
11972
  const EVENT_TYPES = [
11973
11973
  "immediate_response",
@@ -11980,6 +11980,8 @@ var SmoothAgentChat = (function(exports) {
11980
11980
  "otp_sent",
11981
11981
  "otp_verified",
11982
11982
  "otp_invalid",
11983
+ "interaction_required",
11984
+ "interaction_invalid",
11983
11985
  "error",
11984
11986
  "pong"
11985
11987
  ];
@@ -11988,7 +11990,7 @@ var SmoothAgentChat = (function(exports) {
11988
11990
  return typeof frame === "object" && frame !== null && "type" in frame && typeof frame.type === "string" && EVENT_TYPES.includes(frame.type);
11989
11991
  }
11990
11992
  //#endregion
11991
- //#region node_modules/.pnpm/@smooai+smooth-operator@1.8.0/node_modules/@smooai/smooth-operator/dist/client.js
11993
+ //#region node_modules/.pnpm/@smooai+smooth-operator@1.21.1/node_modules/@smooai/smooth-operator/dist/client.js
11992
11994
  /**
11993
11995
  * SmoothAgentClient — a minimal, idiomatic, transport-agnostic client for the
11994
11996
  * smooth-operator WebSocket protocol.
@@ -12273,6 +12275,20 @@ var SmoothAgentChat = (function(exports) {
12273
12275
  ...req
12274
12276
  }));
12275
12277
  }
12278
+ /**
12279
+ * Submit (or decline) a Rich Interaction, resuming the turn parked by an
12280
+ * `interaction_required` event. This ONE verb serves every interaction kind
12281
+ * (identity intake, future date pickers, choice chips, …) — adding a kind
12282
+ * needs no new client method. Server-side validation may reply with an
12283
+ * `interaction_invalid` event (the turn stays parked — resubmit); a valid
12284
+ * submit resumes the stream back into the original {@link MessageTurn}.
12285
+ */
12286
+ submitInteraction(req) {
12287
+ this.transport.send(JSON.stringify({
12288
+ action: "submit_interaction",
12289
+ ...req
12290
+ }));
12291
+ }
12276
12292
  /** Send an action that expects a single correlated response event. */
12277
12293
  request(action) {
12278
12294
  const requestId = action.requestId ?? this.generateRequestId();
@@ -12855,6 +12871,13 @@ var SmoothAgentChat = (function(exports) {
12855
12871
  return null;
12856
12872
  }
12857
12873
  }
12874
+ /**
12875
+ * The Rich-Interaction render capabilities this widget declares at session
12876
+ * create (`supports`). Must stay aligned with the card registry in
12877
+ * `element.ts` (`INTERACTION_CARDS`) — registering a card IS declaring its
12878
+ * capability; a test asserts the two match.
12879
+ */
12880
+ const SUPPORTED_INTERACTION_CAPABILITIES = ["identity_form"];
12858
12881
  /** Pull the final assistant text out of an `eventual_response` data payload. */
12859
12882
  function extractFinalText(response) {
12860
12883
  if (!response || typeof response !== "object") return null;
@@ -12932,6 +12955,7 @@ var SmoothAgentChat = (function(exports) {
12932
12955
  _defineProperty(this, "seq", 0);
12933
12956
  _defineProperty(this, "activeRequestId", null);
12934
12957
  _defineProperty(this, "interrupt", null);
12958
+ _defineProperty(this, "pendingInteractionValues", null);
12935
12959
  _defineProperty(this, "identityRestore", { phase: "idle" });
12936
12960
  _defineProperty(this, "resumeAttempted", false);
12937
12961
  _defineProperty(this, "httpBase", void 0);
@@ -13010,6 +13034,40 @@ var SmoothAgentChat = (function(exports) {
13010
13034
  });
13011
13035
  this.setInterrupt(null);
13012
13036
  }
13037
+ /**
13038
+ * Submit a Rich Interaction card to resume the parked turn. The server
13039
+ * routes to the kind's validator: invalid values come back as an
13040
+ * `interaction_invalid` event (the card re-renders with per-field errors —
13041
+ * the turn stays parked); a valid submit is acked and the turn resumes.
13042
+ * No-op if not awaiting an interaction.
13043
+ */
13044
+ submitInteraction(values) {
13045
+ if (!this.client || !this.sessionId || !this.activeRequestId || this.interrupt?.kind !== "interaction") return;
13046
+ this.pendingInteractionValues = {
13047
+ kind: this.interrupt.interactionKind,
13048
+ values
13049
+ };
13050
+ this.client.submitInteraction({
13051
+ sessionId: this.sessionId,
13052
+ requestId: this.activeRequestId,
13053
+ interactionId: this.interrupt.interactionId,
13054
+ kind: this.interrupt.interactionKind,
13055
+ values
13056
+ });
13057
+ }
13058
+ /** Decline the pending Rich Interaction; the agent continues without it. */
13059
+ declineInteraction() {
13060
+ if (!this.client || !this.sessionId || !this.activeRequestId || this.interrupt?.kind !== "interaction") return;
13061
+ this.client.submitInteraction({
13062
+ sessionId: this.sessionId,
13063
+ requestId: this.activeRequestId,
13064
+ interactionId: this.interrupt.interactionId,
13065
+ kind: this.interrupt.interactionKind,
13066
+ declined: true
13067
+ });
13068
+ this.pendingInteractionValues = null;
13069
+ this.setInterrupt(null);
13070
+ }
13013
13071
  nextId(prefix) {
13014
13072
  this.seq += 1;
13015
13073
  return `${prefix}-${this.seq}-${Date.now().toString(36)}`;
@@ -13174,6 +13232,7 @@ var SmoothAgentChat = (function(exports) {
13174
13232
  userName: state.identity.name,
13175
13233
  userEmail: state.identity.email,
13176
13234
  browserFingerprint: this.fingerprint(),
13235
+ supports: [...SUPPORTED_INTERACTION_CAPABILITIES],
13177
13236
  ...metadata ? { metadata } : {}
13178
13237
  });
13179
13238
  this.sessionId = session.sessionId;
@@ -13316,6 +13375,55 @@ var SmoothAgentChat = (function(exports) {
13316
13375
  actionDescription: str(inner.actionDescription)
13317
13376
  });
13318
13377
  break;
13378
+ case "interaction_required": {
13379
+ const interactionId = str(inner.interactionId);
13380
+ const kind = str(inner.kind);
13381
+ const spec = inner.spec && typeof inner.spec === "object" ? inner.spec : {};
13382
+ if (!interactionId || !kind) break;
13383
+ this.pendingInteractionValues = null;
13384
+ this.setInterrupt({
13385
+ kind: "interaction",
13386
+ interactionId,
13387
+ interactionKind: kind,
13388
+ spec,
13389
+ reason: str(inner.reason)
13390
+ });
13391
+ break;
13392
+ }
13393
+ case "interaction_invalid":
13394
+ if (this.interrupt?.kind === "interaction" && this.interrupt.interactionId === str(inner.interactionId)) {
13395
+ const errors = [];
13396
+ if (Array.isArray(inner.errors)) for (const e of inner.errors) {
13397
+ if (!e || typeof e !== "object") continue;
13398
+ const o = e;
13399
+ const field = str(o.field);
13400
+ if (field) errors.push({
13401
+ field,
13402
+ message: str(o.message) ?? "Invalid value"
13403
+ });
13404
+ }
13405
+ this.pendingInteractionValues = null;
13406
+ this.setInterrupt({
13407
+ ...this.interrupt,
13408
+ errors
13409
+ });
13410
+ }
13411
+ break;
13412
+ case "immediate_response":
13413
+ if (this.interrupt?.kind === "interaction") {
13414
+ const pending = this.pendingInteractionValues;
13415
+ if (pending && pending.kind === "identity_intake") {
13416
+ const v = pending.values;
13417
+ this.store.getState().mergeIdentity({
13418
+ name: v.name,
13419
+ email: v.email,
13420
+ phone: v.phone
13421
+ });
13422
+ }
13423
+ this.pendingInteractionValues = null;
13424
+ this.setInterrupt(null);
13425
+ }
13426
+ break;
13319
13427
  default: break;
13320
13428
  }
13321
13429
  }
@@ -14128,6 +14236,9 @@ ${mode === "fullpage" ? `
14128
14236
  border-color: color-mix(in srgb, var(--sac-primary) 60%, transparent);
14129
14237
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--sac-primary) 14%, transparent);
14130
14238
  }
14239
+ .int-form { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
14240
+ .int-form .pc-field input { width: 100%; box-sizing: border-box; }
14241
+ .int-form .int-error { margin-top: 2px; }
14131
14242
  .int-btn {
14132
14243
  border: 1px solid color-mix(in srgb, var(--sac-border) 80%, transparent);
14133
14244
  background: var(--sac-surface-2);
@@ -14147,8 +14258,14 @@ ${mode === "fullpage" ? `
14147
14258
  color: var(--sac-primary-text);
14148
14259
  box-shadow: 0 6px 14px -6px color-mix(in srgb, var(--sac-primary) 65%, transparent);
14149
14260
  }
14150
- .int-row .int-btn { flex: 1; }
14151
- .int-row .int-input + .int-btn { flex: 0 0 auto; }
14261
+ .int-row .int-form { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
14262
+ .int-form .pc-field input { width: 100%; box-sizing: border-box; }
14263
+ .int-form .int-error { margin-top: 2px; }
14264
+ .int-btn { flex: 1; }
14265
+ .int-row .int-input + .int-form { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
14266
+ .int-form .pc-field input { width: 100%; box-sizing: border-box; }
14267
+ .int-form .int-error { margin-top: 2px; }
14268
+ .int-btn { flex: 0 0 auto; }
14152
14269
  .int-error { margin-top: 8px; font-size: 12px; color: #f87171; }
14153
14270
  .int-card { position: relative; }
14154
14271
  .int-close {
@@ -14286,9 +14403,139 @@ ${mode === "fullpage" ? `
14286
14403
  chev: `<svg width="11" height="11" viewBox="0 0 24 24" fill="none"><path d="m9 6 6 6-6 6" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>`,
14287
14404
  /** OTP interrupt — a padlock. */
14288
14405
  lock: `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="5" y="10.5" width="14" height="9.5" rx="2.2" stroke="currentColor" stroke-width="1.7"/><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5" stroke="currentColor" stroke-width="1.7"/></svg>`,
14406
+ /** Identity-intake interrupt — a person. */
14407
+ user: `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="8.2" r="3.4" stroke="currentColor" stroke-width="1.7"/><path d="M5.5 19.5c.8-3.1 3.4-4.8 6.5-4.8s5.7 1.7 6.5 4.8" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>`,
14289
14408
  /** Tool-confirmation interrupt — a shield. */
14290
14409
  shield: `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 3 5 6v5c0 4.4 3 7.2 7 8.5 4-1.3 7-4.1 7-8.5V6l-7-3Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="m9 11.5 2 2 4-4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>`
14291
14410
  };
14411
+ /**
14412
+ * The identity_intake card: the fields the agent requested (pre-chat form field
14413
+ * pattern — same classes, same phone formatting), per-field server errors, a
14414
+ * submit and a decline affordance. Server-supplied text (reason, labels, error
14415
+ * messages) is set via `textContent` — never innerHTML.
14416
+ */
14417
+ function buildIdentityIntakeCard(it, ctx) {
14418
+ const form = document.createElement("form");
14419
+ form.className = "int-form";
14420
+ form.noValidate = true;
14421
+ if (it.reason) {
14422
+ const desc = document.createElement("div");
14423
+ desc.className = "int-desc";
14424
+ desc.textContent = it.reason;
14425
+ form.appendChild(desc);
14426
+ }
14427
+ const DEFAULTS = {
14428
+ name: {
14429
+ label: "Name",
14430
+ type: "text",
14431
+ autocomplete: "name"
14432
+ },
14433
+ email: {
14434
+ label: "Email",
14435
+ type: "email",
14436
+ autocomplete: "email"
14437
+ },
14438
+ phone: {
14439
+ label: "Phone",
14440
+ type: "tel",
14441
+ autocomplete: "tel"
14442
+ }
14443
+ };
14444
+ const rawFields = Array.isArray(it.spec.fields) ? it.spec.fields : [];
14445
+ const fields = [];
14446
+ for (const f of rawFields) {
14447
+ if (!f || typeof f !== "object") continue;
14448
+ const o = f;
14449
+ const key = typeof o.key === "string" ? o.key : "";
14450
+ if (key !== "name" && key !== "email" && key !== "phone") continue;
14451
+ fields.push({
14452
+ key,
14453
+ required: o.required === true,
14454
+ label: typeof o.label === "string" ? o.label : void 0
14455
+ });
14456
+ }
14457
+ if (fields.length === 0) fields.push({
14458
+ key: "email",
14459
+ required: true
14460
+ });
14461
+ for (const f of fields) {
14462
+ const d = DEFAULTS[f.key];
14463
+ const label = document.createElement("label");
14464
+ label.className = "pc-field";
14465
+ const caption = document.createElement("span");
14466
+ caption.textContent = f.label ?? d.label;
14467
+ const input = document.createElement("input");
14468
+ input.name = f.key;
14469
+ input.type = d.type;
14470
+ input.setAttribute("autocomplete", d.autocomplete);
14471
+ input.required = f.required;
14472
+ const prefill = ctx.prefill[f.key];
14473
+ if (prefill) input.value = prefill;
14474
+ label.append(caption, input);
14475
+ if (f.key === "phone") {
14476
+ const hint = document.createElement("span");
14477
+ hint.className = "pc-hint";
14478
+ hint.setAttribute("aria-live", "polite");
14479
+ label.appendChild(hint);
14480
+ }
14481
+ const serverError = it.errors?.find((e) => e.field === f.key);
14482
+ if (serverError) {
14483
+ label.classList.add("invalid");
14484
+ const err = document.createElement("span");
14485
+ err.className = "int-error";
14486
+ err.textContent = serverError.message;
14487
+ label.appendChild(err);
14488
+ }
14489
+ form.appendChild(label);
14490
+ }
14491
+ const row = document.createElement("div");
14492
+ row.className = "int-row";
14493
+ const decline = document.createElement("button");
14494
+ decline.className = "int-btn";
14495
+ decline.type = "button";
14496
+ decline.textContent = "No thanks";
14497
+ decline.addEventListener("click", () => ctx.decline());
14498
+ const share = document.createElement("button");
14499
+ share.className = "int-btn primary";
14500
+ share.type = "submit";
14501
+ share.textContent = "Share details";
14502
+ row.append(decline, share);
14503
+ form.appendChild(row);
14504
+ form.addEventListener("submit", (ev) => {
14505
+ ev.preventDefault();
14506
+ if (!form.reportValidity()) return;
14507
+ const data = new FormData(form);
14508
+ const val = (k) => data.get(k)?.trim() || void 0;
14509
+ const rawPhone = val("phone");
14510
+ const phoneInput = form.querySelector("input[name=\"phone\"]");
14511
+ if (rawPhone && phoneInput && !isValidPhoneNumber(rawPhone, PHONE_DEFAULT_REGION)) {
14512
+ const field = phoneInput.closest(".pc-field");
14513
+ field?.classList.add("invalid");
14514
+ const hint = field?.querySelector(".pc-hint");
14515
+ if (hint) hint.textContent = "Enter a valid phone number";
14516
+ if (phoneInput.hasAttribute("required")) {
14517
+ phoneInput.focus();
14518
+ return;
14519
+ }
14520
+ }
14521
+ const phone = rawPhone ? phoneToE164(rawPhone) ?? rawPhone : void 0;
14522
+ ctx.submit({
14523
+ name: val("name"),
14524
+ email: val("email"),
14525
+ phone
14526
+ });
14527
+ });
14528
+ ctx.wirePhoneField(form);
14529
+ queueMicrotask(() => form.querySelector("input")?.focus());
14530
+ return form;
14531
+ }
14532
+ /** Kind → card. See the registry doc above. */
14533
+ const INTERACTION_CARDS = { identity_intake: {
14534
+ capability: "identity_form",
14535
+ title: "Share your details",
14536
+ icon: ICON.user,
14537
+ build: buildIdentityIntakeCard
14538
+ } };
14292
14539
  var SmoothAgentChatElement = class extends HTMLElement {
14293
14540
  static get observedAttributes() {
14294
14541
  return OBSERVED;
@@ -14571,19 +14818,31 @@ ${mode === "fullpage" ? `
14571
14818
  head.className = "int-head";
14572
14819
  const ico = document.createElement("span");
14573
14820
  ico.className = "int-ico";
14574
- ico.innerHTML = it.kind === "otp" ? ICON.lock : ICON.shield;
14821
+ const card_meta = it.kind === "interaction" ? INTERACTION_CARDS[it.interactionKind] : void 0;
14822
+ if (it.kind === "interaction" && !card_meta) {
14823
+ this.controller?.declineInteraction();
14824
+ el.classList.add("hidden");
14825
+ return;
14826
+ }
14827
+ ico.innerHTML = it.kind === "otp" ? ICON.lock : it.kind === "interaction" ? card_meta?.icon ?? ICON.user : ICON.shield;
14575
14828
  const title = document.createElement("span");
14576
14829
  title.className = "int-title";
14577
- title.textContent = it.kind === "otp" ? "Verification required" : "Confirm this action";
14830
+ title.textContent = it.kind === "otp" ? "Verification required" : it.kind === "interaction" ? card_meta?.title ?? "One more thing" : "Confirm this action";
14578
14831
  head.append(ico, title);
14579
14832
  card.appendChild(head);
14580
- if (it.actionDescription) {
14833
+ if (it.kind !== "interaction" && it.actionDescription) {
14581
14834
  const desc = document.createElement("div");
14582
14835
  desc.className = "int-desc";
14583
14836
  desc.textContent = it.actionDescription;
14584
14837
  card.appendChild(desc);
14585
14838
  }
14586
- if (it.kind === "otp") {
14839
+ if (it.kind === "interaction") card.appendChild(card_meta.build(it, {
14840
+ submit: (values) => this.controller?.submitInteraction(values),
14841
+ decline: () => this.controller?.declineInteraction(),
14842
+ prefill: this.controller?.getStore().getState().identity ?? {},
14843
+ wirePhoneField: (form) => this.wirePhoneField(form)
14844
+ }));
14845
+ else if (it.kind === "otp") {
14587
14846
  if (it.sent?.maskedDestination) {
14588
14847
  const sent = document.createElement("div");
14589
14848
  sent.className = "int-sent";