@syntrologie/adapt-chatbot 2.37.2 → 2.39.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/dist/AdaptiveChatBarMountable.d.ts.map +1 -1
- package/dist/ChatSession.d.ts +23 -0
- package/dist/ChatSession.d.ts.map +1 -1
- package/dist/ChatTransport.d.ts.map +1 -1
- package/dist/LeaveAMessageLit.d.ts +112 -0
- package/dist/LeaveAMessageLit.d.ts.map +1 -0
- package/dist/LeaveAMessageLit.test.d.ts +2 -0
- package/dist/LeaveAMessageLit.test.d.ts.map +1 -0
- package/dist/LeaveAMessageMountable.test.d.ts +2 -0
- package/dist/LeaveAMessageMountable.test.d.ts.map +1 -0
- package/dist/cdn.d.ts +13 -2
- package/dist/cdn.d.ts.map +1 -1
- package/dist/runtime.d.ts +50 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +420 -1
- package/dist/runtime.js.map +4 -4
- package/dist/schema.d.ts +36 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +111 -0
- package/dist/schema.js.map +2 -2
- package/dist/support-chat/boot.d.ts +12 -0
- package/dist/support-chat/boot.d.ts.map +1 -0
- package/dist/support-chat/boot.test.d.ts +2 -0
- package/dist/support-chat/boot.test.d.ts.map +1 -0
- package/dist/support-chat/crisp.d.ts +4 -0
- package/dist/support-chat/crisp.d.ts.map +1 -0
- package/dist/support-chat/crisp.test.d.ts +2 -0
- package/dist/support-chat/crisp.test.d.ts.map +1 -0
- package/dist/support-chat/registry.d.ts +5 -0
- package/dist/support-chat/registry.d.ts.map +1 -0
- package/dist/support-chat/registry.test.d.ts +2 -0
- package/dist/support-chat/registry.test.d.ts.map +1 -0
- package/dist/support-chat/types.d.ts +19 -0
- package/dist/support-chat/types.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/runtime.js
CHANGED
|
@@ -23,7 +23,8 @@ import "./chunk-OIGVZGSX.js";
|
|
|
23
23
|
import {
|
|
24
24
|
DIVE_DEEPER_EVENT,
|
|
25
25
|
TILE_MOUNTED_EVENT,
|
|
26
|
-
TILE_UNMOUNTED_EVENT
|
|
26
|
+
TILE_UNMOUNTED_EVENT,
|
|
27
|
+
stripMountPlumbing
|
|
27
28
|
} from "./chunk-EACGDSJI.js";
|
|
28
29
|
import "./chunk-UVKRO5ER.js";
|
|
29
30
|
|
|
@@ -265,6 +266,33 @@ var ChatSession = class {
|
|
|
265
266
|
msg.toolCalls = [...msg.toolCalls ?? [], { ...toolCall }];
|
|
266
267
|
this.notify();
|
|
267
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Create an empty, already-settled assistant message at `id` if one
|
|
271
|
+
* doesn't already exist. Idempotent — safe to call on every tool-call
|
|
272
|
+
* sighting.
|
|
273
|
+
*
|
|
274
|
+
* Exists for TOOL-ONLY turns: pydantic-ai's AG-UI adapter mints a fresh
|
|
275
|
+
* `parent_message_id` per model response regardless of content
|
|
276
|
+
* (`before_response` → `self.new_message_id()`) and stamps it on every
|
|
277
|
+
* `TOOL_CALL_START`, even when the response has no text. When the
|
|
278
|
+
* model's entire action is one or more tool calls with no narration
|
|
279
|
+
* (a parallel `mount_*` + `suggest_replies` batch, most commonly), no
|
|
280
|
+
* `TEXT_MESSAGE_START` is ever emitted for that id, so nothing would
|
|
281
|
+
* otherwise create the host message `addToolCall` needs — and the tool
|
|
282
|
+
* call (and any chips/receipts derived from it) silently vanishes
|
|
283
|
+
* (BUG-1786654199). Call this before `addToolCall` on a tool call's
|
|
284
|
+
* first sighting so it always has a message to land on.
|
|
285
|
+
*
|
|
286
|
+
* Deliberately status:'complete' (not 'streaming' via `receiveStart`):
|
|
287
|
+
* a tool-only message has no text to stream, so there's nothing for a
|
|
288
|
+
* later `receiveEnd` to settle — leaving it 'streaming' would paint
|
|
289
|
+
* the trail's streaming/typing visual state forever.
|
|
290
|
+
*/
|
|
291
|
+
ensureAssistantMessage(id) {
|
|
292
|
+
if (this._messages.some((m) => m.id === id)) return;
|
|
293
|
+
this._messages.push({ id, role: "assistant", text: "", status: "complete" });
|
|
294
|
+
this.notify();
|
|
295
|
+
}
|
|
268
296
|
/**
|
|
269
297
|
* Partially update a tool call by id. Used by the transport to
|
|
270
298
|
* advance status (args-streaming → running → done) as AG-UI events
|
|
@@ -1221,6 +1249,7 @@ var ChatTransport = class {
|
|
|
1221
1249
|
if (!targetMessageId) return;
|
|
1222
1250
|
const parsedArgs = parseToolCallArgs(event.toolCall);
|
|
1223
1251
|
if (!existing) {
|
|
1252
|
+
chatSession.ensureAssistantMessage(targetMessageId);
|
|
1224
1253
|
chatSession.addToolCall(targetMessageId, {
|
|
1225
1254
|
id: event.toolCall.id,
|
|
1226
1255
|
name: event.toolCall.name,
|
|
@@ -1770,6 +1799,70 @@ function busEventToRawEvent(e) {
|
|
|
1770
1799
|
return { event: e.name, timestamp: ts, properties: { ...e.props ?? {} } };
|
|
1771
1800
|
}
|
|
1772
1801
|
|
|
1802
|
+
// src/support-chat/registry.ts
|
|
1803
|
+
var bridges = /* @__PURE__ */ new Map();
|
|
1804
|
+
function registerSupportChatBridge(bridge) {
|
|
1805
|
+
bridges.set(bridge.vendor, bridge);
|
|
1806
|
+
}
|
|
1807
|
+
function getSupportChatBridge(vendor) {
|
|
1808
|
+
return bridges.get(vendor);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// src/support-chat/crisp.ts
|
|
1812
|
+
function crisp() {
|
|
1813
|
+
if (typeof window === "undefined") return void 0;
|
|
1814
|
+
return window.$crisp;
|
|
1815
|
+
}
|
|
1816
|
+
function push(cmd) {
|
|
1817
|
+
try {
|
|
1818
|
+
crisp()?.push(cmd);
|
|
1819
|
+
} catch (err) {
|
|
1820
|
+
console.warn("supportChat.bridge: crisp push failed", cmd[0], cmd[1], err);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
var crispBridge = {
|
|
1824
|
+
vendor: "crisp",
|
|
1825
|
+
detect: () => crisp() !== void 0,
|
|
1826
|
+
hideNative: () => push(["do", "chat:hide"]),
|
|
1827
|
+
setVisitorEmail: (email) => push(["set", "user:email", [email]]),
|
|
1828
|
+
sendMessage: (text) => push(["do", "message:send", ["text", text]]),
|
|
1829
|
+
onOperatorReply: (cb) => {
|
|
1830
|
+
let active = true;
|
|
1831
|
+
push([
|
|
1832
|
+
"on",
|
|
1833
|
+
"message:received",
|
|
1834
|
+
(msg) => {
|
|
1835
|
+
if (!active) return;
|
|
1836
|
+
push(["do", "chat:hide"]);
|
|
1837
|
+
if (msg?.from !== "operator") return;
|
|
1838
|
+
if (typeof msg?.content === "string") cb(msg.content);
|
|
1839
|
+
}
|
|
1840
|
+
]);
|
|
1841
|
+
return () => {
|
|
1842
|
+
active = false;
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
function registerCrispBridge() {
|
|
1847
|
+
registerSupportChatBridge(crispBridge);
|
|
1848
|
+
}
|
|
1849
|
+
registerCrispBridge();
|
|
1850
|
+
|
|
1851
|
+
// src/support-chat/boot.ts
|
|
1852
|
+
function bootSupportChat(cfg) {
|
|
1853
|
+
if (!cfg?.vendor) return void 0;
|
|
1854
|
+
registerCrispBridge();
|
|
1855
|
+
const bridge = getSupportChatBridge(cfg.vendor);
|
|
1856
|
+
if (!bridge || !bridge.detect()) {
|
|
1857
|
+
console.warn(
|
|
1858
|
+
`supportChat.bridge: vendor '${cfg.vendor}' not detected on this page \u2014 handoff disabled`
|
|
1859
|
+
);
|
|
1860
|
+
return void 0;
|
|
1861
|
+
}
|
|
1862
|
+
if (cfg.suppressNative) bridge.hideNative();
|
|
1863
|
+
return bridge;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1773
1866
|
// src/AdaptiveChatBarMountable.ts
|
|
1774
1867
|
var STATE_KEY = "__syntroChatBarMount";
|
|
1775
1868
|
function getState(container) {
|
|
@@ -2060,6 +2153,7 @@ var AdaptiveChatBarMountable = {
|
|
|
2060
2153
|
mount(container, mountConfig) {
|
|
2061
2154
|
const cfg = mountConfig ?? {};
|
|
2062
2155
|
configureTransportIfPossible(cfg);
|
|
2156
|
+
bootSupportChat(cfg.supportChat);
|
|
2063
2157
|
wireDiveDeeperListener();
|
|
2064
2158
|
const bar = document.createElement("adaptive-chat-bar");
|
|
2065
2159
|
applyPlaceholder(bar, cfg);
|
|
@@ -2196,6 +2290,266 @@ var AdaptiveChipsStripMountable = {
|
|
|
2196
2290
|
}
|
|
2197
2291
|
};
|
|
2198
2292
|
|
|
2293
|
+
// src/LeaveAMessageLit.ts
|
|
2294
|
+
import { html, LitElement, nothing } from "lit";
|
|
2295
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
2296
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2297
|
+
var box = {
|
|
2298
|
+
display: "flex",
|
|
2299
|
+
flexDirection: "column",
|
|
2300
|
+
gap: "var(--sc-content-item-gap, 10px)",
|
|
2301
|
+
fontFamily: "var(--sc-font-family, system-ui)",
|
|
2302
|
+
fontSize: "var(--sc-content-body-font-size, 13px)"
|
|
2303
|
+
};
|
|
2304
|
+
var fieldStyles = {
|
|
2305
|
+
padding: "var(--sc-content-item-padding, 10px 12px)",
|
|
2306
|
+
borderRadius: "var(--sc-content-border-radius, 8px)",
|
|
2307
|
+
border: "var(--sc-content-bubble-border, 1px solid currentColor)",
|
|
2308
|
+
background: "var(--sc-content-bubble-background, transparent)",
|
|
2309
|
+
color: "inherit",
|
|
2310
|
+
font: "inherit",
|
|
2311
|
+
// The browser's default focus ring is a loud blue on a black-and-white
|
|
2312
|
+
// brand — the one saturated pixel on the card. Suppress it and paint our
|
|
2313
|
+
// own from the theme in the focus/blur handlers below.
|
|
2314
|
+
outline: "none",
|
|
2315
|
+
width: "100%",
|
|
2316
|
+
boxSizing: "border-box",
|
|
2317
|
+
// The drag grabber pokes out of the card's bottom-right corner and lets
|
|
2318
|
+
// the visitor break the tile frame — the card owns its own geometry.
|
|
2319
|
+
resize: "none"
|
|
2320
|
+
};
|
|
2321
|
+
var buttonStyles = {
|
|
2322
|
+
padding: "11px 14px",
|
|
2323
|
+
borderRadius: "var(--sc-border-radius, 8px)",
|
|
2324
|
+
border: "none",
|
|
2325
|
+
background: "var(--sc-color-primary, #000000)",
|
|
2326
|
+
color: "var(--sc-accent-foreground, #ffffff)",
|
|
2327
|
+
fontWeight: "600",
|
|
2328
|
+
cursor: "pointer",
|
|
2329
|
+
font: "inherit",
|
|
2330
|
+
width: "100%",
|
|
2331
|
+
boxSizing: "border-box"
|
|
2332
|
+
};
|
|
2333
|
+
var textStyles = {
|
|
2334
|
+
margin: "0"
|
|
2335
|
+
};
|
|
2336
|
+
var mutedTextStyles = {
|
|
2337
|
+
margin: "0",
|
|
2338
|
+
// No opacity token exists in any publishable section — plain constant.
|
|
2339
|
+
opacity: "0.8"
|
|
2340
|
+
};
|
|
2341
|
+
var PERSIST_NAMESPACE = "supportchat";
|
|
2342
|
+
var PERSIST_KEY = "leave-a-message";
|
|
2343
|
+
var LeaveAMessageLit = class extends LitElement {
|
|
2344
|
+
constructor() {
|
|
2345
|
+
super(...arguments);
|
|
2346
|
+
this.message = "";
|
|
2347
|
+
this.contextSummary = "";
|
|
2348
|
+
this.state = "form";
|
|
2349
|
+
this._email = "";
|
|
2350
|
+
this._reply = "";
|
|
2351
|
+
this._operatorMessages = [];
|
|
2352
|
+
/** Paint our own focus ring from the theme — see fieldStyles.outline. */
|
|
2353
|
+
this._focusOn = (e) => {
|
|
2354
|
+
const el = e.target;
|
|
2355
|
+
el.style.outline = "2px solid var(--sc-color-primary, currentColor)";
|
|
2356
|
+
el.style.outlineOffset = "1px";
|
|
2357
|
+
};
|
|
2358
|
+
this._focusOff = (e) => {
|
|
2359
|
+
const el = e.target;
|
|
2360
|
+
el.style.outline = "none";
|
|
2361
|
+
};
|
|
2362
|
+
this._onReply = (text) => {
|
|
2363
|
+
this._operatorMessages = [...this._operatorMessages, text];
|
|
2364
|
+
if (this.state !== "flipped") {
|
|
2365
|
+
this.state = "mail";
|
|
2366
|
+
this.runtime?.events.publish("supportchat:reply_received", { text });
|
|
2367
|
+
this._toastReplyArrived();
|
|
2368
|
+
this.dispatchEvent(
|
|
2369
|
+
new CustomEvent("syntro:supportchat-reply", {
|
|
2370
|
+
bubbles: true,
|
|
2371
|
+
composed: true,
|
|
2372
|
+
detail: { text }
|
|
2373
|
+
})
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
this._persist();
|
|
2377
|
+
this.requestUpdate();
|
|
2378
|
+
};
|
|
2379
|
+
this._submit = () => {
|
|
2380
|
+
if (!this.bridge || !EMAIL_RE.test(this._email)) return;
|
|
2381
|
+
this.bridge.setVisitorEmail(this._email);
|
|
2382
|
+
const context = this.contextSummary ? `[Site assistant context: ${this.contextSummary}]
|
|
2383
|
+
` : "";
|
|
2384
|
+
this.bridge.sendMessage(`${context}${this.message}`);
|
|
2385
|
+
this.state = "sent";
|
|
2386
|
+
this._persist();
|
|
2387
|
+
};
|
|
2388
|
+
this._flip = () => {
|
|
2389
|
+
this.state = "flipped";
|
|
2390
|
+
this._persist();
|
|
2391
|
+
};
|
|
2392
|
+
this._sendReply = () => {
|
|
2393
|
+
if (!this.bridge || !this._reply.trim()) return;
|
|
2394
|
+
this.bridge.sendMessage(this._reply.trim());
|
|
2395
|
+
this._reply = "";
|
|
2396
|
+
this.requestUpdate();
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
createRenderRoot() {
|
|
2400
|
+
return this;
|
|
2401
|
+
}
|
|
2402
|
+
connectedCallback() {
|
|
2403
|
+
super.connectedCallback();
|
|
2404
|
+
this._loadPersisted();
|
|
2405
|
+
}
|
|
2406
|
+
updated() {
|
|
2407
|
+
if (this.state !== "form" && this.bridge && this.bridge.detect() && !this._unsubscribe) {
|
|
2408
|
+
this._unsubscribe = this.bridge.onOperatorReply(this._onReply);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
disconnectedCallback() {
|
|
2412
|
+
this._unsubscribe?.();
|
|
2413
|
+
this._unsubscribe = void 0;
|
|
2414
|
+
super.disconnectedCallback();
|
|
2415
|
+
}
|
|
2416
|
+
/** Read the injected persistence store, if any (I2). */
|
|
2417
|
+
_persistStore() {
|
|
2418
|
+
return this.runtime?.state?.user?.ns?.(PERSIST_NAMESPACE);
|
|
2419
|
+
}
|
|
2420
|
+
/** Hydrate {state, operatorMessages, email} from a prior page's mount. */
|
|
2421
|
+
_loadPersisted() {
|
|
2422
|
+
const store = this._persistStore();
|
|
2423
|
+
if (!store) return;
|
|
2424
|
+
const saved = store.get(PERSIST_KEY);
|
|
2425
|
+
if (!saved) return;
|
|
2426
|
+
if (saved.state) this.state = saved.state;
|
|
2427
|
+
if (Array.isArray(saved.operatorMessages)) this._operatorMessages = saved.operatorMessages;
|
|
2428
|
+
if (typeof saved.email === "string") this._email = saved.email;
|
|
2429
|
+
}
|
|
2430
|
+
/** Snapshot {state, operatorMessages, email} so the NEXT mount (a fresh
|
|
2431
|
+
* page in the same session) can pick the thread back up. */
|
|
2432
|
+
_persist() {
|
|
2433
|
+
const store = this._persistStore();
|
|
2434
|
+
if (!store) return;
|
|
2435
|
+
const state = this.state;
|
|
2436
|
+
const operatorMessages = this._operatorMessages;
|
|
2437
|
+
const email = this._email;
|
|
2438
|
+
const snapshot = { state, operatorMessages, email };
|
|
2439
|
+
store.set(PERSIST_KEY, snapshot);
|
|
2440
|
+
}
|
|
2441
|
+
/**
|
|
2442
|
+
* Raise the reply toast through the canvas element. Pierces shadow roots to
|
|
2443
|
+
* find `<smart-canvas>` (same resolution AdaptiveChatBarMountable uses), and
|
|
2444
|
+
* no-ops when no canvas is present — the tile's own "You got mail!" face is
|
|
2445
|
+
* always the durable signal.
|
|
2446
|
+
*/
|
|
2447
|
+
_toastReplyArrived() {
|
|
2448
|
+
if (typeof document === "undefined") return;
|
|
2449
|
+
const title = this.replyToast?.title?.trim();
|
|
2450
|
+
if (!title) return;
|
|
2451
|
+
const find = (root) => {
|
|
2452
|
+
const direct = root.querySelector("smart-canvas");
|
|
2453
|
+
if (direct) return direct;
|
|
2454
|
+
for (const el of root.querySelectorAll("*")) {
|
|
2455
|
+
const sr = el.shadowRoot;
|
|
2456
|
+
if (sr) {
|
|
2457
|
+
const found = find(sr);
|
|
2458
|
+
if (found) return found;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
return null;
|
|
2462
|
+
};
|
|
2463
|
+
try {
|
|
2464
|
+
const canvas = find(document);
|
|
2465
|
+
canvas?.previewNotification?.({ title, body: this.replyToast?.body });
|
|
2466
|
+
} catch (err) {
|
|
2467
|
+
console.warn("supportChat.widget: reply toast failed", err);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
render() {
|
|
2471
|
+
if (!this.bridge || !this.bridge.detect()) return nothing;
|
|
2472
|
+
if (this.state === "form") {
|
|
2473
|
+
return html`<div data-lam style=${styleMap(box)}>
|
|
2474
|
+
<input
|
|
2475
|
+
data-lam-email
|
|
2476
|
+
type="email"
|
|
2477
|
+
placeholder="Your email — so the team can reach you"
|
|
2478
|
+
style=${styleMap(fieldStyles)}
|
|
2479
|
+
@focus=${this._focusOn}
|
|
2480
|
+
@blur=${this._focusOff}
|
|
2481
|
+
@input=${(e) => {
|
|
2482
|
+
this._email = e.target.value;
|
|
2483
|
+
this.requestUpdate();
|
|
2484
|
+
}}
|
|
2485
|
+
/>
|
|
2486
|
+
<textarea
|
|
2487
|
+
data-lam-message
|
|
2488
|
+
rows="4"
|
|
2489
|
+
style=${styleMap(fieldStyles)}
|
|
2490
|
+
@focus=${this._focusOn}
|
|
2491
|
+
@blur=${this._focusOff}
|
|
2492
|
+
.value=${this.message}
|
|
2493
|
+
@input=${(e) => {
|
|
2494
|
+
this.message = e.target.value;
|
|
2495
|
+
}}
|
|
2496
|
+
></textarea>
|
|
2497
|
+
<button type="button" data-lam-send style=${styleMap(buttonStyles)} @click=${this._submit}>
|
|
2498
|
+
Send to the team
|
|
2499
|
+
</button>
|
|
2500
|
+
</div>`;
|
|
2501
|
+
}
|
|
2502
|
+
if (this.state === "sent") {
|
|
2503
|
+
return html`<div data-lam style=${styleMap(box)}>
|
|
2504
|
+
<p style=${styleMap(mutedTextStyles)}>
|
|
2505
|
+
Sent. The team will reply here — or by email if you step away.
|
|
2506
|
+
</p>
|
|
2507
|
+
</div>`;
|
|
2508
|
+
}
|
|
2509
|
+
if (this.state === "mail") {
|
|
2510
|
+
return html`<div data-lam style=${styleMap(box)}>
|
|
2511
|
+
<button type="button" data-lam-mail style=${styleMap(buttonStyles)} @click=${this._flip}>
|
|
2512
|
+
📬 You got mail! — tap to read
|
|
2513
|
+
</button>
|
|
2514
|
+
</div>`;
|
|
2515
|
+
}
|
|
2516
|
+
return html`<div data-lam style=${styleMap(box)}>
|
|
2517
|
+
${this._operatorMessages.map(
|
|
2518
|
+
(m) => html`<p data-lam-operator style=${styleMap(textStyles)}>${m}</p>`
|
|
2519
|
+
)}
|
|
2520
|
+
<input
|
|
2521
|
+
data-lam-reply
|
|
2522
|
+
type="text"
|
|
2523
|
+
placeholder="Reply to the team…"
|
|
2524
|
+
style=${styleMap(fieldStyles)}
|
|
2525
|
+
@focus=${this._focusOn}
|
|
2526
|
+
@blur=${this._focusOff}
|
|
2527
|
+
.value=${this._reply}
|
|
2528
|
+
@input=${(e) => {
|
|
2529
|
+
this._reply = e.target.value;
|
|
2530
|
+
}}
|
|
2531
|
+
/>
|
|
2532
|
+
<button type="button" data-lam-reply-send style=${styleMap(buttonStyles)} @click=${this._sendReply}>
|
|
2533
|
+
Send
|
|
2534
|
+
</button>
|
|
2535
|
+
</div>`;
|
|
2536
|
+
}
|
|
2537
|
+
};
|
|
2538
|
+
LeaveAMessageLit.properties = {
|
|
2539
|
+
bridge: { attribute: false },
|
|
2540
|
+
message: { type: String },
|
|
2541
|
+
contextSummary: { attribute: false },
|
|
2542
|
+
state: { type: String },
|
|
2543
|
+
runtime: { attribute: false },
|
|
2544
|
+
replyToast: { attribute: false }
|
|
2545
|
+
};
|
|
2546
|
+
function registerLeaveAMessageLit() {
|
|
2547
|
+
if (typeof customElements !== "undefined" && !customElements.get("syntro-leave-a-message")) {
|
|
2548
|
+
customElements.define("syntro-leave-a-message", LeaveAMessageLit);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
registerLeaveAMessageLit();
|
|
2552
|
+
|
|
2199
2553
|
// src/NavLinkMountable.ts
|
|
2200
2554
|
var STATE_KEY3 = "__syntroNavLinkMount";
|
|
2201
2555
|
var SAFE_NAVIGATION_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
@@ -2317,6 +2671,58 @@ var TextAnswerMountable = {
|
|
|
2317
2671
|
};
|
|
2318
2672
|
|
|
2319
2673
|
// src/runtime.ts
|
|
2674
|
+
var LEAVE_A_MESSAGE_STATE_KEY = "__syntroLeaveAMessageMount";
|
|
2675
|
+
function getLeaveAMessageState(container) {
|
|
2676
|
+
return container[LEAVE_A_MESSAGE_STATE_KEY] ?? null;
|
|
2677
|
+
}
|
|
2678
|
+
function setLeaveAMessageState(container, state) {
|
|
2679
|
+
container[LEAVE_A_MESSAGE_STATE_KEY] = state;
|
|
2680
|
+
}
|
|
2681
|
+
var LeaveAMessageMountable = {
|
|
2682
|
+
mount(container, config) {
|
|
2683
|
+
registerLeaveAMessageLit();
|
|
2684
|
+
const el = document.createElement("syntro-leave-a-message");
|
|
2685
|
+
const incoming = config ?? null;
|
|
2686
|
+
const stripped = stripMountPlumbing(incoming) ?? {};
|
|
2687
|
+
const supportChat = stripped.supportChat;
|
|
2688
|
+
const runtime2 = incoming?.runtime;
|
|
2689
|
+
Object.assign(el, {
|
|
2690
|
+
bridge: bootSupportChat(supportChat),
|
|
2691
|
+
message: stripped.message ?? "",
|
|
2692
|
+
contextSummary: stripped.context_summary ?? "",
|
|
2693
|
+
// Visitor-facing toast copy from the workspace config — see
|
|
2694
|
+
// LeaveAMessageLit#replyToast (presentation is configuration).
|
|
2695
|
+
replyToast: stripped.replyToast,
|
|
2696
|
+
runtime: runtime2
|
|
2697
|
+
});
|
|
2698
|
+
container.appendChild(el);
|
|
2699
|
+
setLeaveAMessageState(container, { el });
|
|
2700
|
+
return () => {
|
|
2701
|
+
setLeaveAMessageState(container, null);
|
|
2702
|
+
el.remove();
|
|
2703
|
+
};
|
|
2704
|
+
},
|
|
2705
|
+
/**
|
|
2706
|
+
* I1 (final-review fixes 2026-08-13): without this, `WidgetRegistry`
|
|
2707
|
+
* falls back to unmount+remount on every config patch (e.g. the agent
|
|
2708
|
+
* editing the prefilled `message` mid-conversation), which destroys the
|
|
2709
|
+
* live element and with it the visitor's in-flight escalation state
|
|
2710
|
+
* (email already captured, an operator reply already sitting in
|
|
2711
|
+
* `mail`/`flipped`). Mirrors `AdaptiveChatBarMountable.update`'s
|
|
2712
|
+
* pattern: patch the presentational props in place, touch nothing
|
|
2713
|
+
* else. `state`, `bridge`, and the operator-message thread are NEVER
|
|
2714
|
+
* reset here.
|
|
2715
|
+
*/
|
|
2716
|
+
update(container, config) {
|
|
2717
|
+
const mounted = getLeaveAMessageState(container);
|
|
2718
|
+
if (!mounted) return;
|
|
2719
|
+
const stripped = stripMountPlumbing(config ?? null) ?? {};
|
|
2720
|
+
if (typeof stripped.message === "string") mounted.el.message = stripped.message;
|
|
2721
|
+
if (typeof stripped.context_summary === "string") {
|
|
2722
|
+
mounted.el.contextSummary = stripped.context_summary;
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
};
|
|
2320
2726
|
var runtime = {
|
|
2321
2727
|
id: "adaptive-chatbot",
|
|
2322
2728
|
version: "2.0.0",
|
|
@@ -2387,12 +2793,25 @@ var runtime = {
|
|
|
2387
2793
|
description: "Same-origin navigation button \u2014 used as a chip payload for LLM-authored nav suggestions.",
|
|
2388
2794
|
icon: "\u2192"
|
|
2389
2795
|
}
|
|
2796
|
+
},
|
|
2797
|
+
// Escalation-only async human handoff: email + message into the site's
|
|
2798
|
+
// own support-chat inbox (Crisp first); operator replies flip the tile
|
|
2799
|
+
// ("You got mail!"). See docs/superpowers/specs/2026-08-13-async-handoff-crisp-design.md.
|
|
2800
|
+
{
|
|
2801
|
+
id: "adaptive-chatbot:leave-a-message",
|
|
2802
|
+
component: LeaveAMessageMountable,
|
|
2803
|
+
metadata: {
|
|
2804
|
+
name: "Leave a Message",
|
|
2805
|
+
description: `Escalation-only async human handoff: email + message into the site's own support-chat inbox; operator replies flip the tile ("You got mail!")`,
|
|
2806
|
+
icon: "\u{1F4EC}"
|
|
2807
|
+
}
|
|
2390
2808
|
}
|
|
2391
2809
|
]
|
|
2392
2810
|
};
|
|
2393
2811
|
var runtime_default = runtime;
|
|
2394
2812
|
export {
|
|
2395
2813
|
ElementInstanceStore,
|
|
2814
|
+
LeaveAMessageMountable,
|
|
2396
2815
|
TileHandler,
|
|
2397
2816
|
runtime_default as default,
|
|
2398
2817
|
getElementInstanceStore,
|