@smooai/chat-widget 0.10.1 → 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 +4 -0
- package/dist/chat-widget.global.js +72 -5
- package/dist/chat-widget.global.js.map +1 -1
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +72 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +14 -0
- package/src/conversation.ts +26 -0
- package/src/element.ts +64 -4
- package/src/styles.ts +8 -1
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.
|
|
@@ -228,6 +231,7 @@ Declarative attributes mirror the programmatic [`ChatWidgetConfig`](./src/config
|
|
|
228
231
|
| `greeting` | `greeting` | Shown before the first message. |
|
|
229
232
|
| `mode` | `mode` | `popover` (default) or `fullpage`. |
|
|
230
233
|
| `start-open` | `startOpen` | Open the panel immediately (no launcher click). |
|
|
234
|
+
| `hide-branding` | `hideBranding` | Hide the "powered by smooth-operator" tag + footer link. Default shown. |
|
|
231
235
|
| — | `theme` | Brand colors (see [Theming](#theming)). |
|
|
232
236
|
| — | `userName` / `userEmail` | Optional participant identity. |
|
|
233
237
|
|
|
@@ -11772,7 +11772,9 @@ var SmoothAgentChat = (function(exports) {
|
|
|
11772
11772
|
greeting: config.greeting ?? "Hi! How can I help you today?",
|
|
11773
11773
|
connectionErrorMessage: config.connectionErrorMessage ?? "We couldn't reach the chat. Please try again in a moment.",
|
|
11774
11774
|
startOpen: config.startOpen ?? false,
|
|
11775
|
+
hideBranding: config.hideBranding ?? false,
|
|
11775
11776
|
examplePrompts: (config.examplePrompts ?? []).filter((p) => p.trim().length > 0).slice(0, 5),
|
|
11777
|
+
showSuggestedReplies: config.showSuggestedReplies ?? true,
|
|
11776
11778
|
requireName: config.requireName ?? false,
|
|
11777
11779
|
requireEmail: config.requireEmail ?? false,
|
|
11778
11780
|
requirePhone: config.requirePhone ?? false,
|
|
@@ -12893,6 +12895,19 @@ var SmoothAgentChat = (function(exports) {
|
|
|
12893
12895
|
}
|
|
12894
12896
|
return out;
|
|
12895
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
|
+
}
|
|
12896
12911
|
/** Convert a server message row into a finalized {@link ChatMessage}. */
|
|
12897
12912
|
function wireMessageToChat(m, idx) {
|
|
12898
12913
|
const text = typeof m.content?.text === "string" ? m.content.text : "";
|
|
@@ -13243,6 +13258,8 @@ var SmoothAgentChat = (function(exports) {
|
|
|
13243
13258
|
if (!assistant.text) assistant.text = "(no response)";
|
|
13244
13259
|
const citations = extractCitations(inner);
|
|
13245
13260
|
if (citations.length > 0) assistant.citations = citations;
|
|
13261
|
+
const suggestions = extractSuggestions(inner?.response);
|
|
13262
|
+
if (suggestions.length > 0) assistant.suggestions = suggestions;
|
|
13246
13263
|
assistant.streaming = false;
|
|
13247
13264
|
this.emitMessages();
|
|
13248
13265
|
} catch (err) {
|
|
@@ -13707,7 +13724,8 @@ ${mode === "fullpage" ? `
|
|
|
13707
13724
|
}
|
|
13708
13725
|
.close:hover { background: color-mix(in srgb, var(--sac-text) 12%, transparent); transform: translateY(1px); }
|
|
13709
13726
|
.close svg { width: 16px; height: 16px; opacity: .8; }
|
|
13710
|
-
.powered { margin-left: auto; font-size: 10.5px; letter-spacing: .02em; opacity: .6; }
|
|
13727
|
+
.powered { margin-left: auto; font-size: 10.5px; letter-spacing: .02em; opacity: .6; color: inherit; text-decoration: none; }
|
|
13728
|
+
.powered:hover { opacity: .85; text-decoration: underline; }
|
|
13711
13729
|
.header-sep { height: 1px; margin: 0 16px; background: linear-gradient(90deg, transparent, var(--sac-border), transparent); }
|
|
13712
13730
|
|
|
13713
13731
|
/* Full-page header: taller, logo-led, no close. */
|
|
@@ -13979,6 +13997,8 @@ ${mode === "fullpage" ? `
|
|
|
13979
13997
|
color: color-mix(in srgb, var(--sac-text) 38%, transparent);
|
|
13980
13998
|
}
|
|
13981
13999
|
.footer b { font-weight: 600; color: color-mix(in srgb, var(--sac-text) 55%, transparent); }
|
|
14000
|
+
.footer a { color: inherit; text-decoration: none; }
|
|
14001
|
+
.footer a:hover { text-decoration: underline; }
|
|
13982
14002
|
|
|
13983
14003
|
/* ─────────────────── Pre-chat identity form ───────────────────────── */
|
|
13984
14004
|
.prechat { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 18px; padding: 22px 20px; }
|
|
@@ -14070,6 +14090,10 @@ ${mode === "fullpage" ? `
|
|
|
14070
14090
|
transform: translateY(-1px);
|
|
14071
14091
|
}
|
|
14072
14092
|
|
|
14093
|
+
/* ───────────── Mid-conversation suggested-reply chips ─────────────── */
|
|
14094
|
+
.reply-suggestions { padding: 0 14px 4px; }
|
|
14095
|
+
.reply-suggestions:empty { display: none; }
|
|
14096
|
+
|
|
14073
14097
|
/* ─────────────── OTP / tool-confirmation interrupt ────────────────── */
|
|
14074
14098
|
.interrupt { padding: 0 14px; }
|
|
14075
14099
|
.int-card {
|
|
@@ -14232,6 +14256,8 @@ ${mode === "fullpage" ? `
|
|
|
14232
14256
|
return null;
|
|
14233
14257
|
}
|
|
14234
14258
|
}
|
|
14259
|
+
/** Public smooth-operator repo — the "powered by" header tag + footer link here. */
|
|
14260
|
+
const SMOOTH_OPERATOR_URL = "https://github.com/SmooAI/smooth-operator";
|
|
14235
14261
|
const OBSERVED = [
|
|
14236
14262
|
"endpoint",
|
|
14237
14263
|
"agent-id",
|
|
@@ -14240,7 +14266,8 @@ ${mode === "fullpage" ? `
|
|
|
14240
14266
|
"placeholder",
|
|
14241
14267
|
"greeting",
|
|
14242
14268
|
"start-open",
|
|
14243
|
-
"mode"
|
|
14269
|
+
"mode",
|
|
14270
|
+
"hide-branding"
|
|
14244
14271
|
];
|
|
14245
14272
|
/**
|
|
14246
14273
|
* Inline SVG icons (static, trusted strings — never interpolated with user data).
|
|
@@ -14278,6 +14305,7 @@ ${mode === "fullpage" ? `
|
|
|
14278
14305
|
_defineProperty(this, "userInfoSatisfied", false);
|
|
14279
14306
|
_defineProperty(this, "hasSent", false);
|
|
14280
14307
|
_defineProperty(this, "examplePrompts", []);
|
|
14308
|
+
_defineProperty(this, "showSuggestedReplies", true);
|
|
14281
14309
|
_defineProperty(this, "greeting", "");
|
|
14282
14310
|
_defineProperty(this, "interrupt", null);
|
|
14283
14311
|
_defineProperty(this, "interruptEl", null);
|
|
@@ -14291,6 +14319,7 @@ ${mode === "fullpage" ? `
|
|
|
14291
14319
|
_defineProperty(this, "dotEl", null);
|
|
14292
14320
|
_defineProperty(this, "inputEl", null);
|
|
14293
14321
|
_defineProperty(this, "sendBtn", null);
|
|
14322
|
+
_defineProperty(this, "suggestionsEl", null);
|
|
14294
14323
|
_defineProperty(this, "streamBubbleEl", null);
|
|
14295
14324
|
_defineProperty(this, "streamMsgId", null);
|
|
14296
14325
|
_defineProperty(this, "streamTarget", "");
|
|
@@ -14357,7 +14386,9 @@ ${mode === "fullpage" ? `
|
|
|
14357
14386
|
greeting: this.overrides.greeting ?? this.getAttribute("greeting") ?? void 0,
|
|
14358
14387
|
connectionErrorMessage: this.overrides.connectionErrorMessage,
|
|
14359
14388
|
startOpen: this.overrides.startOpen ?? this.hasAttribute("start-open"),
|
|
14389
|
+
hideBranding: this.overrides.hideBranding ?? this.hasAttribute("hide-branding"),
|
|
14360
14390
|
examplePrompts: this.overrides.examplePrompts,
|
|
14391
|
+
showSuggestedReplies: this.overrides.showSuggestedReplies,
|
|
14361
14392
|
requireName: this.overrides.requireName,
|
|
14362
14393
|
requireEmail: this.overrides.requireEmail,
|
|
14363
14394
|
requirePhone: this.overrides.requirePhone,
|
|
@@ -14389,10 +14420,12 @@ ${mode === "fullpage" ? `
|
|
|
14389
14420
|
onInterrupt: (interrupt) => {
|
|
14390
14421
|
this.interrupt = interrupt;
|
|
14391
14422
|
this.renderInterrupt();
|
|
14423
|
+
this.renderSuggestions();
|
|
14392
14424
|
},
|
|
14393
14425
|
onIdentityRestore: (state) => {
|
|
14394
14426
|
this.identityRestore = state;
|
|
14395
14427
|
this.renderInterrupt();
|
|
14428
|
+
this.renderSuggestions();
|
|
14396
14429
|
}
|
|
14397
14430
|
});
|
|
14398
14431
|
if (resolved.startOpen) this.open = true;
|
|
@@ -14410,7 +14443,7 @@ ${mode === "fullpage" ? `
|
|
|
14410
14443
|
<span class="title">${escapeHtml(resolved.agentName)}</span>
|
|
14411
14444
|
<span class="status"><span class="dot off"></span><span class="status-text"></span></span>
|
|
14412
14445
|
</div>
|
|
14413
|
-
|
|
14446
|
+
${resolved.hideBranding ? "" : `<a class="powered" href="${SMOOTH_OPERATOR_URL}" target="_blank" rel="noopener noreferrer">powered by smooth-operator</a>`}
|
|
14414
14447
|
</div>` : `<div class="header">
|
|
14415
14448
|
<div class="avatar">${monogram}</div>
|
|
14416
14449
|
<div class="meta">
|
|
@@ -14420,6 +14453,7 @@ ${mode === "fullpage" ? `
|
|
|
14420
14453
|
<button class="close" aria-label="Close chat">${ICON.close}</button>
|
|
14421
14454
|
</div>`;
|
|
14422
14455
|
this.examplePrompts = resolved.examplePrompts;
|
|
14456
|
+
this.showSuggestedReplies = resolved.showSuggestedReplies;
|
|
14423
14457
|
this.greeting = resolved.greeting;
|
|
14424
14458
|
const gating = needsUserInfo(resolved) && !this.userInfoSatisfied;
|
|
14425
14459
|
this.gating = gating;
|
|
@@ -14444,16 +14478,18 @@ ${mode === "fullpage" ? `
|
|
|
14444
14478
|
<button type="submit" class="pc-submit">Start chat</button>
|
|
14445
14479
|
</form>
|
|
14446
14480
|
</div>`;
|
|
14447
|
-
const
|
|
14481
|
+
const footerInner = [resolved.hideBranding ? "" : `<a href="${SMOOTH_OPERATOR_URL}" target="_blank" rel="noopener noreferrer">powered by <b>smooth‑operator</b></a>`, this.allowChatRestore ? `<button type="button" class="restore-link">Restore my chats</button>` : ""].filter(Boolean).join(" · ");
|
|
14482
|
+
const footerHtml = footerInner ? `<div class="footer">${footerInner}</div>` : "";
|
|
14448
14483
|
const chatHtml = `
|
|
14449
14484
|
<div class="messages"></div>
|
|
14485
|
+
<div class="reply-suggestions"></div>
|
|
14450
14486
|
<div class="interrupt hidden"></div>
|
|
14451
14487
|
<div class="composer-wrap">
|
|
14452
14488
|
<div class="composer">
|
|
14453
14489
|
<textarea rows="1" placeholder="${escapeHtml(resolved.placeholder)}"></textarea>
|
|
14454
14490
|
<button class="send" type="button" aria-label="Send message">${ICON.send}</button>
|
|
14455
14491
|
</div>
|
|
14456
|
-
|
|
14492
|
+
${footerHtml}
|
|
14457
14493
|
</div>`;
|
|
14458
14494
|
const container = document.createElement("div");
|
|
14459
14495
|
container.className = "wrap";
|
|
@@ -14477,6 +14513,7 @@ ${mode === "fullpage" ? `
|
|
|
14477
14513
|
this.inputEl = container.querySelector("textarea");
|
|
14478
14514
|
this.sendBtn = container.querySelector(".send");
|
|
14479
14515
|
this.interruptEl = container.querySelector(".interrupt");
|
|
14516
|
+
this.suggestionsEl = container.querySelector(".reply-suggestions");
|
|
14480
14517
|
this.launcherEl?.addEventListener("click", () => this.openChat());
|
|
14481
14518
|
container.querySelector(".close")?.addEventListener("click", () => this.closeChat());
|
|
14482
14519
|
this.sendBtn?.addEventListener("click", () => this.submit());
|
|
@@ -14931,6 +14968,36 @@ ${mode === "fullpage" ? `
|
|
|
14931
14968
|
if (msg.role === "assistant" && !msg.streaming && msg.citations && msg.citations.length > 0) this.messagesEl.appendChild(this.renderSources(msg.citations));
|
|
14932
14969
|
}
|
|
14933
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);
|
|
14934
15001
|
}
|
|
14935
15002
|
/** True when the host/user prefers reduced motion (snap, no typewriter). */
|
|
14936
15003
|
prefersReducedMotion() {
|