@smooai/chat-widget 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -186,6 +186,9 @@ mountChatWidget({
186
186
  ```
187
187
 
188
188
  - **`examplePrompts`** render as clickable chips in the empty state (max 5).
189
+ - **`showSuggestedReplies`** (default `true`) shows mid-conversation suggested-reply
190
+ chips under the latest assistant message when the agent returns follow-up
191
+ suggestions; tapping one sends it. Set `false` to hide them.
189
192
  - **`requireName` / `requireEmail` / `requirePhone`** show a styled pre-chat form;
190
193
  the collected name/email are attached to the conversation session (phone rides
191
194
  session metadata). Set **`allowAnonymous: true`** to skip the form.
@@ -11774,6 +11774,7 @@ var SmoothAgentChat = (function(exports) {
11774
11774
  startOpen: config.startOpen ?? false,
11775
11775
  hideBranding: config.hideBranding ?? false,
11776
11776
  examplePrompts: (config.examplePrompts ?? []).filter((p) => p.trim().length > 0).slice(0, 5),
11777
+ showSuggestedReplies: config.showSuggestedReplies ?? true,
11777
11778
  requireName: config.requireName ?? false,
11778
11779
  requireEmail: config.requireEmail ?? false,
11779
11780
  requirePhone: config.requirePhone ?? false,
@@ -12894,6 +12895,19 @@ var SmoothAgentChat = (function(exports) {
12894
12895
  }
12895
12896
  return out;
12896
12897
  }
12898
+ /**
12899
+ * Pull the suggested follow-up replies out of a terminal `eventual_response`'s
12900
+ * `response` object (`response.suggestedNextActions`). Optional + back-compatible
12901
+ * like citations — read defensively (tolerating absence, non-array shapes, and
12902
+ * non-string / blank entries) and capped at 4 so a chatty agent can't overflow
12903
+ * the composer chip row.
12904
+ */
12905
+ function extractSuggestions(response) {
12906
+ if (!response || typeof response !== "object") return [];
12907
+ const raw = response.suggestedNextActions;
12908
+ if (!Array.isArray(raw)) return [];
12909
+ return raw.filter((s) => typeof s === "string" && s.trim().length > 0).slice(0, 4);
12910
+ }
12897
12911
  /** Convert a server message row into a finalized {@link ChatMessage}. */
12898
12912
  function wireMessageToChat(m, idx) {
12899
12913
  const text = typeof m.content?.text === "string" ? m.content.text : "";
@@ -13244,6 +13258,8 @@ var SmoothAgentChat = (function(exports) {
13244
13258
  if (!assistant.text) assistant.text = "(no response)";
13245
13259
  const citations = extractCitations(inner);
13246
13260
  if (citations.length > 0) assistant.citations = citations;
13261
+ const suggestions = extractSuggestions(inner?.response);
13262
+ if (suggestions.length > 0) assistant.suggestions = suggestions;
13247
13263
  assistant.streaming = false;
13248
13264
  this.emitMessages();
13249
13265
  } catch (err) {
@@ -14074,6 +14090,10 @@ ${mode === "fullpage" ? `
14074
14090
  transform: translateY(-1px);
14075
14091
  }
14076
14092
 
14093
+ /* ───────────── Mid-conversation suggested-reply chips ─────────────── */
14094
+ .reply-suggestions { padding: 0 14px 4px; }
14095
+ .reply-suggestions:empty { display: none; }
14096
+
14077
14097
  /* ─────────────── OTP / tool-confirmation interrupt ────────────────── */
14078
14098
  .interrupt { padding: 0 14px; }
14079
14099
  .int-card {
@@ -14285,6 +14305,7 @@ ${mode === "fullpage" ? `
14285
14305
  _defineProperty(this, "userInfoSatisfied", false);
14286
14306
  _defineProperty(this, "hasSent", false);
14287
14307
  _defineProperty(this, "examplePrompts", []);
14308
+ _defineProperty(this, "showSuggestedReplies", true);
14288
14309
  _defineProperty(this, "greeting", "");
14289
14310
  _defineProperty(this, "interrupt", null);
14290
14311
  _defineProperty(this, "interruptEl", null);
@@ -14298,6 +14319,7 @@ ${mode === "fullpage" ? `
14298
14319
  _defineProperty(this, "dotEl", null);
14299
14320
  _defineProperty(this, "inputEl", null);
14300
14321
  _defineProperty(this, "sendBtn", null);
14322
+ _defineProperty(this, "suggestionsEl", null);
14301
14323
  _defineProperty(this, "streamBubbleEl", null);
14302
14324
  _defineProperty(this, "streamMsgId", null);
14303
14325
  _defineProperty(this, "streamTarget", "");
@@ -14366,6 +14388,7 @@ ${mode === "fullpage" ? `
14366
14388
  startOpen: this.overrides.startOpen ?? this.hasAttribute("start-open"),
14367
14389
  hideBranding: this.overrides.hideBranding ?? this.hasAttribute("hide-branding"),
14368
14390
  examplePrompts: this.overrides.examplePrompts,
14391
+ showSuggestedReplies: this.overrides.showSuggestedReplies,
14369
14392
  requireName: this.overrides.requireName,
14370
14393
  requireEmail: this.overrides.requireEmail,
14371
14394
  requirePhone: this.overrides.requirePhone,
@@ -14397,10 +14420,12 @@ ${mode === "fullpage" ? `
14397
14420
  onInterrupt: (interrupt) => {
14398
14421
  this.interrupt = interrupt;
14399
14422
  this.renderInterrupt();
14423
+ this.renderSuggestions();
14400
14424
  },
14401
14425
  onIdentityRestore: (state) => {
14402
14426
  this.identityRestore = state;
14403
14427
  this.renderInterrupt();
14428
+ this.renderSuggestions();
14404
14429
  }
14405
14430
  });
14406
14431
  if (resolved.startOpen) this.open = true;
@@ -14428,6 +14453,7 @@ ${mode === "fullpage" ? `
14428
14453
  <button class="close" aria-label="Close chat">${ICON.close}</button>
14429
14454
  </div>`;
14430
14455
  this.examplePrompts = resolved.examplePrompts;
14456
+ this.showSuggestedReplies = resolved.showSuggestedReplies;
14431
14457
  this.greeting = resolved.greeting;
14432
14458
  const gating = needsUserInfo(resolved) && !this.userInfoSatisfied;
14433
14459
  this.gating = gating;
@@ -14456,6 +14482,7 @@ ${mode === "fullpage" ? `
14456
14482
  const footerHtml = footerInner ? `<div class="footer">${footerInner}</div>` : "";
14457
14483
  const chatHtml = `
14458
14484
  <div class="messages"></div>
14485
+ <div class="reply-suggestions"></div>
14459
14486
  <div class="interrupt hidden"></div>
14460
14487
  <div class="composer-wrap">
14461
14488
  <div class="composer">
@@ -14486,6 +14513,7 @@ ${mode === "fullpage" ? `
14486
14513
  this.inputEl = container.querySelector("textarea");
14487
14514
  this.sendBtn = container.querySelector(".send");
14488
14515
  this.interruptEl = container.querySelector(".interrupt");
14516
+ this.suggestionsEl = container.querySelector(".reply-suggestions");
14489
14517
  this.launcherEl?.addEventListener("click", () => this.openChat());
14490
14518
  container.querySelector(".close")?.addEventListener("click", () => this.closeChat());
14491
14519
  this.sendBtn?.addEventListener("click", () => this.submit());
@@ -14940,6 +14968,36 @@ ${mode === "fullpage" ? `
14940
14968
  if (msg.role === "assistant" && !msg.streaming && msg.citations && msg.citations.length > 0) this.messagesEl.appendChild(this.renderSources(msg.citations));
14941
14969
  }
14942
14970
  this.scrollToBottom(true);
14971
+ this.renderSuggestions();
14972
+ }
14973
+ /**
14974
+ * Render (or clear) the mid-conversation suggested-reply chips under the
14975
+ * composer. Shown ONLY when: the feature is enabled, no interrupt / restore
14976
+ * overlay is active (those reuse the slot above the composer), and the LATEST
14977
+ * message is a finalized assistant turn that carried suggestions. A new turn
14978
+ * (agent or the visitor typing) appends messages, so the latest is no longer
14979
+ * that assistant message and the chips clear on the next render. Chips reuse
14980
+ * the empty-state `.prompts`/`.chip` styling and send via the same path.
14981
+ */
14982
+ renderSuggestions() {
14983
+ const el = this.suggestionsEl;
14984
+ if (!el) return;
14985
+ el.replaceChildren();
14986
+ if (!this.showSuggestedReplies) return;
14987
+ if (this.interrupt || this.identityRestore.phase !== "idle") return;
14988
+ const last = this.messages[this.messages.length - 1];
14989
+ if (!last || last.role !== "assistant" || last.streaming || !last.suggestions || last.suggestions.length === 0) return;
14990
+ const chips = document.createElement("div");
14991
+ chips.className = "prompts";
14992
+ for (const suggestion of last.suggestions) {
14993
+ const chip = document.createElement("button");
14994
+ chip.type = "button";
14995
+ chip.className = "chip";
14996
+ chip.textContent = suggestion;
14997
+ chip.addEventListener("click", () => this.submitPrompt(suggestion));
14998
+ chips.appendChild(chip);
14999
+ }
15000
+ el.appendChild(chips);
14943
15001
  }
14944
15002
  /** True when the host/user prefers reduced motion (snap, no typewriter). */
14945
15003
  prefersReducedMotion() {