@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 +3 -0
- package/dist/chat-widget.global.js +58 -0
- package/dist/chat-widget.global.js.map +1 -1
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +7 -0
- package/src/conversation.ts +26 -0
- package/src/element.ts +44 -0
- package/src/styles.ts +4 -0
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() {
|