@smooai/chat-widget 0.11.0 → 0.13.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 +1 -0
- package/dist/chat-widget.global.js +473 -21
- package/dist/chat-widget.global.js.map +1 -1
- package/dist/index.d.ts +333 -236
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +454 -18
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/config.ts +11 -0
- package/src/conversation.ts +205 -3
- package/src/element.ts +315 -18
- package/src/index.ts +2 -0
- package/src/styles.ts +53 -2
|
@@ -11782,6 +11782,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
11782
11782
|
collectConsent: config.collectConsent ?? true,
|
|
11783
11783
|
allowChatRestore: config.allowChatRestore ?? true,
|
|
11784
11784
|
allowAnonymous: config.allowAnonymous ?? false,
|
|
11785
|
+
showToolActivity: config.showToolActivity ?? false,
|
|
11785
11786
|
theme: {
|
|
11786
11787
|
text: theme.text ?? "#f8fafc",
|
|
11787
11788
|
background: theme.background ?? "#040d30",
|
|
@@ -11842,7 +11843,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
11842
11843
|
}) : e[r] = t, e;
|
|
11843
11844
|
}
|
|
11844
11845
|
//#endregion
|
|
11845
|
-
//#region node_modules/.pnpm/@smooai+smooth-operator@1.
|
|
11846
|
+
//#region node_modules/.pnpm/@smooai+smooth-operator@1.21.1/node_modules/@smooai/smooth-operator/dist/transport.js
|
|
11846
11847
|
/**
|
|
11847
11848
|
* Transport abstraction for the client.
|
|
11848
11849
|
*
|
|
@@ -11967,7 +11968,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
11967
11968
|
}
|
|
11968
11969
|
};
|
|
11969
11970
|
//#endregion
|
|
11970
|
-
//#region node_modules/.pnpm/@smooai+smooth-operator@1.
|
|
11971
|
+
//#region node_modules/.pnpm/@smooai+smooth-operator@1.21.1/node_modules/@smooai/smooth-operator/dist/types.js
|
|
11971
11972
|
/** Every server→client `type` discriminator value. */
|
|
11972
11973
|
const EVENT_TYPES = [
|
|
11973
11974
|
"immediate_response",
|
|
@@ -11980,6 +11981,8 @@ var SmoothAgentChat = (function(exports) {
|
|
|
11980
11981
|
"otp_sent",
|
|
11981
11982
|
"otp_verified",
|
|
11982
11983
|
"otp_invalid",
|
|
11984
|
+
"interaction_required",
|
|
11985
|
+
"interaction_invalid",
|
|
11983
11986
|
"error",
|
|
11984
11987
|
"pong"
|
|
11985
11988
|
];
|
|
@@ -11988,7 +11991,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
11988
11991
|
return typeof frame === "object" && frame !== null && "type" in frame && typeof frame.type === "string" && EVENT_TYPES.includes(frame.type);
|
|
11989
11992
|
}
|
|
11990
11993
|
//#endregion
|
|
11991
|
-
//#region node_modules/.pnpm/@smooai+smooth-operator@1.
|
|
11994
|
+
//#region node_modules/.pnpm/@smooai+smooth-operator@1.21.1/node_modules/@smooai/smooth-operator/dist/client.js
|
|
11992
11995
|
/**
|
|
11993
11996
|
* SmoothAgentClient — a minimal, idiomatic, transport-agnostic client for the
|
|
11994
11997
|
* smooth-operator WebSocket protocol.
|
|
@@ -12273,6 +12276,20 @@ var SmoothAgentChat = (function(exports) {
|
|
|
12273
12276
|
...req
|
|
12274
12277
|
}));
|
|
12275
12278
|
}
|
|
12279
|
+
/**
|
|
12280
|
+
* Submit (or decline) a Rich Interaction, resuming the turn parked by an
|
|
12281
|
+
* `interaction_required` event. This ONE verb serves every interaction kind
|
|
12282
|
+
* (identity intake, future date pickers, choice chips, …) — adding a kind
|
|
12283
|
+
* needs no new client method. Server-side validation may reply with an
|
|
12284
|
+
* `interaction_invalid` event (the turn stays parked — resubmit); a valid
|
|
12285
|
+
* submit resumes the stream back into the original {@link MessageTurn}.
|
|
12286
|
+
*/
|
|
12287
|
+
submitInteraction(req) {
|
|
12288
|
+
this.transport.send(JSON.stringify({
|
|
12289
|
+
action: "submit_interaction",
|
|
12290
|
+
...req
|
|
12291
|
+
}));
|
|
12292
|
+
}
|
|
12276
12293
|
/** Send an action that expects a single correlated response event. */
|
|
12277
12294
|
request(action) {
|
|
12278
12295
|
const requestId = action.requestId ?? this.generateRequestId();
|
|
@@ -12855,6 +12872,13 @@ var SmoothAgentChat = (function(exports) {
|
|
|
12855
12872
|
return null;
|
|
12856
12873
|
}
|
|
12857
12874
|
}
|
|
12875
|
+
/**
|
|
12876
|
+
* The Rich-Interaction render capabilities this widget declares at session
|
|
12877
|
+
* create (`supports`). Must stay aligned with the card registry in
|
|
12878
|
+
* `element.ts` (`INTERACTION_CARDS`) — registering a card IS declaring its
|
|
12879
|
+
* capability; a test asserts the two match.
|
|
12880
|
+
*/
|
|
12881
|
+
const SUPPORTED_INTERACTION_CAPABILITIES = ["identity_form"];
|
|
12858
12882
|
/** Pull the final assistant text out of an `eventual_response` data payload. */
|
|
12859
12883
|
function extractFinalText(response) {
|
|
12860
12884
|
if (!response || typeof response !== "object") return null;
|
|
@@ -12920,6 +12944,59 @@ var SmoothAgentChat = (function(exports) {
|
|
|
12920
12944
|
streaming: false
|
|
12921
12945
|
};
|
|
12922
12946
|
}
|
|
12947
|
+
let toolSeq = 0;
|
|
12948
|
+
const nextToolId = () => `tool-${++toolSeq}`;
|
|
12949
|
+
/** Grow the trailing text block, or open a new one if the last block was a tool. */
|
|
12950
|
+
function growTextBlock(blocks, text) {
|
|
12951
|
+
if (!text) return;
|
|
12952
|
+
const last = blocks[blocks.length - 1];
|
|
12953
|
+
if (last && last.kind === "text") last.text += text;
|
|
12954
|
+
else blocks.push({
|
|
12955
|
+
kind: "text",
|
|
12956
|
+
text
|
|
12957
|
+
});
|
|
12958
|
+
}
|
|
12959
|
+
/**
|
|
12960
|
+
* Fold a `stream_chunk` node-state into the ordered block list, returning `true`
|
|
12961
|
+
* when the chunk carried tool activity.
|
|
12962
|
+
*
|
|
12963
|
+
* Tool activity rides `state.rawResponse.toolCall` / `state.rawResponse.toolResult`
|
|
12964
|
+
* — **NOT** `state.toolResult`. Reading the wrong path leaves every chip stuck on
|
|
12965
|
+
* "running…" forever (the exact bug the daemon SPA hit and this mirror avoids).
|
|
12966
|
+
*/
|
|
12967
|
+
function applyToolChunk(blocks, state) {
|
|
12968
|
+
const raw = state?.rawResponse;
|
|
12969
|
+
if (!raw || typeof raw !== "object") return false;
|
|
12970
|
+
const call = raw.toolCall;
|
|
12971
|
+
const res = raw.toolResult;
|
|
12972
|
+
if (call) {
|
|
12973
|
+
const args = typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments ?? {});
|
|
12974
|
+
blocks.push({
|
|
12975
|
+
kind: "tool",
|
|
12976
|
+
tool: {
|
|
12977
|
+
id: nextToolId(),
|
|
12978
|
+
name: call.name ?? "",
|
|
12979
|
+
args,
|
|
12980
|
+
done: false
|
|
12981
|
+
}
|
|
12982
|
+
});
|
|
12983
|
+
return true;
|
|
12984
|
+
}
|
|
12985
|
+
if (res) {
|
|
12986
|
+
const result = typeof res.result === "string" ? res.result : JSON.stringify(res.result ?? "");
|
|
12987
|
+
for (let i = blocks.length - 1; i >= 0; i--) {
|
|
12988
|
+
const b = blocks[i];
|
|
12989
|
+
if (b && b.kind === "tool" && b.tool.name === (res.name ?? "") && !b.tool.done) {
|
|
12990
|
+
b.tool.done = true;
|
|
12991
|
+
b.tool.isError = !!res.isError;
|
|
12992
|
+
b.tool.result = result;
|
|
12993
|
+
break;
|
|
12994
|
+
}
|
|
12995
|
+
}
|
|
12996
|
+
return true;
|
|
12997
|
+
}
|
|
12998
|
+
return false;
|
|
12999
|
+
}
|
|
12923
13000
|
var ConversationController = class {
|
|
12924
13001
|
constructor(config, events, store) {
|
|
12925
13002
|
_defineProperty(this, "config", void 0);
|
|
@@ -12932,6 +13009,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
12932
13009
|
_defineProperty(this, "seq", 0);
|
|
12933
13010
|
_defineProperty(this, "activeRequestId", null);
|
|
12934
13011
|
_defineProperty(this, "interrupt", null);
|
|
13012
|
+
_defineProperty(this, "pendingInteractionValues", null);
|
|
12935
13013
|
_defineProperty(this, "identityRestore", { phase: "idle" });
|
|
12936
13014
|
_defineProperty(this, "resumeAttempted", false);
|
|
12937
13015
|
_defineProperty(this, "httpBase", void 0);
|
|
@@ -13010,6 +13088,40 @@ var SmoothAgentChat = (function(exports) {
|
|
|
13010
13088
|
});
|
|
13011
13089
|
this.setInterrupt(null);
|
|
13012
13090
|
}
|
|
13091
|
+
/**
|
|
13092
|
+
* Submit a Rich Interaction card to resume the parked turn. The server
|
|
13093
|
+
* routes to the kind's validator: invalid values come back as an
|
|
13094
|
+
* `interaction_invalid` event (the card re-renders with per-field errors —
|
|
13095
|
+
* the turn stays parked); a valid submit is acked and the turn resumes.
|
|
13096
|
+
* No-op if not awaiting an interaction.
|
|
13097
|
+
*/
|
|
13098
|
+
submitInteraction(values) {
|
|
13099
|
+
if (!this.client || !this.sessionId || !this.activeRequestId || this.interrupt?.kind !== "interaction") return;
|
|
13100
|
+
this.pendingInteractionValues = {
|
|
13101
|
+
kind: this.interrupt.interactionKind,
|
|
13102
|
+
values
|
|
13103
|
+
};
|
|
13104
|
+
this.client.submitInteraction({
|
|
13105
|
+
sessionId: this.sessionId,
|
|
13106
|
+
requestId: this.activeRequestId,
|
|
13107
|
+
interactionId: this.interrupt.interactionId,
|
|
13108
|
+
kind: this.interrupt.interactionKind,
|
|
13109
|
+
values
|
|
13110
|
+
});
|
|
13111
|
+
}
|
|
13112
|
+
/** Decline the pending Rich Interaction; the agent continues without it. */
|
|
13113
|
+
declineInteraction() {
|
|
13114
|
+
if (!this.client || !this.sessionId || !this.activeRequestId || this.interrupt?.kind !== "interaction") return;
|
|
13115
|
+
this.client.submitInteraction({
|
|
13116
|
+
sessionId: this.sessionId,
|
|
13117
|
+
requestId: this.activeRequestId,
|
|
13118
|
+
interactionId: this.interrupt.interactionId,
|
|
13119
|
+
kind: this.interrupt.interactionKind,
|
|
13120
|
+
declined: true
|
|
13121
|
+
});
|
|
13122
|
+
this.pendingInteractionValues = null;
|
|
13123
|
+
this.setInterrupt(null);
|
|
13124
|
+
}
|
|
13013
13125
|
nextId(prefix) {
|
|
13014
13126
|
this.seq += 1;
|
|
13015
13127
|
return `${prefix}-${this.seq}-${Date.now().toString(36)}`;
|
|
@@ -13174,6 +13286,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
13174
13286
|
userName: state.identity.name,
|
|
13175
13287
|
userEmail: state.identity.email,
|
|
13176
13288
|
browserFingerprint: this.fingerprint(),
|
|
13289
|
+
supports: [...SUPPORTED_INTERACTION_CAPABILITIES],
|
|
13177
13290
|
...metadata ? { metadata } : {}
|
|
13178
13291
|
});
|
|
13179
13292
|
this.sessionId = session.sessionId;
|
|
@@ -13230,11 +13343,13 @@ var SmoothAgentChat = (function(exports) {
|
|
|
13230
13343
|
text: trimmed,
|
|
13231
13344
|
streaming: false
|
|
13232
13345
|
});
|
|
13346
|
+
const showTools = this.config.showToolActivity === true;
|
|
13233
13347
|
const assistant = {
|
|
13234
13348
|
id: this.nextId("a"),
|
|
13235
13349
|
role: "assistant",
|
|
13236
13350
|
text: "",
|
|
13237
|
-
streaming: true
|
|
13351
|
+
streaming: true,
|
|
13352
|
+
blocks: showTools ? [] : void 0
|
|
13238
13353
|
};
|
|
13239
13354
|
this.messages.push(assistant);
|
|
13240
13355
|
this.emitMessages();
|
|
@@ -13249,8 +13364,11 @@ var SmoothAgentChat = (function(exports) {
|
|
|
13249
13364
|
const token = event.token ?? event.data?.token ?? "";
|
|
13250
13365
|
if (token) {
|
|
13251
13366
|
assistant.text += token;
|
|
13367
|
+
if (showTools && assistant.blocks) growTextBlock(assistant.blocks, token);
|
|
13252
13368
|
this.emitMessages();
|
|
13253
13369
|
}
|
|
13370
|
+
} else if (showTools && event.type === "stream_chunk") {
|
|
13371
|
+
if (assistant.blocks && applyToolChunk(assistant.blocks, event.data?.state)) this.emitMessages();
|
|
13254
13372
|
} else this.handleTurnEvent(event);
|
|
13255
13373
|
const inner = (await turn).data?.data;
|
|
13256
13374
|
const finalText = extractFinalText(inner?.response);
|
|
@@ -13260,6 +13378,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
13260
13378
|
if (citations.length > 0) assistant.citations = citations;
|
|
13261
13379
|
const suggestions = extractSuggestions(inner?.response);
|
|
13262
13380
|
if (suggestions.length > 0) assistant.suggestions = suggestions;
|
|
13381
|
+
if (assistant.blocks && !assistant.blocks.some((b) => b.kind === "tool")) assistant.blocks = void 0;
|
|
13263
13382
|
assistant.streaming = false;
|
|
13264
13383
|
this.emitMessages();
|
|
13265
13384
|
} catch (err) {
|
|
@@ -13316,6 +13435,55 @@ var SmoothAgentChat = (function(exports) {
|
|
|
13316
13435
|
actionDescription: str(inner.actionDescription)
|
|
13317
13436
|
});
|
|
13318
13437
|
break;
|
|
13438
|
+
case "interaction_required": {
|
|
13439
|
+
const interactionId = str(inner.interactionId);
|
|
13440
|
+
const kind = str(inner.kind);
|
|
13441
|
+
const spec = inner.spec && typeof inner.spec === "object" ? inner.spec : {};
|
|
13442
|
+
if (!interactionId || !kind) break;
|
|
13443
|
+
this.pendingInteractionValues = null;
|
|
13444
|
+
this.setInterrupt({
|
|
13445
|
+
kind: "interaction",
|
|
13446
|
+
interactionId,
|
|
13447
|
+
interactionKind: kind,
|
|
13448
|
+
spec,
|
|
13449
|
+
reason: str(inner.reason)
|
|
13450
|
+
});
|
|
13451
|
+
break;
|
|
13452
|
+
}
|
|
13453
|
+
case "interaction_invalid":
|
|
13454
|
+
if (this.interrupt?.kind === "interaction" && this.interrupt.interactionId === str(inner.interactionId)) {
|
|
13455
|
+
const errors = [];
|
|
13456
|
+
if (Array.isArray(inner.errors)) for (const e of inner.errors) {
|
|
13457
|
+
if (!e || typeof e !== "object") continue;
|
|
13458
|
+
const o = e;
|
|
13459
|
+
const field = str(o.field);
|
|
13460
|
+
if (field) errors.push({
|
|
13461
|
+
field,
|
|
13462
|
+
message: str(o.message) ?? "Invalid value"
|
|
13463
|
+
});
|
|
13464
|
+
}
|
|
13465
|
+
this.pendingInteractionValues = null;
|
|
13466
|
+
this.setInterrupt({
|
|
13467
|
+
...this.interrupt,
|
|
13468
|
+
errors
|
|
13469
|
+
});
|
|
13470
|
+
}
|
|
13471
|
+
break;
|
|
13472
|
+
case "immediate_response":
|
|
13473
|
+
if (this.interrupt?.kind === "interaction") {
|
|
13474
|
+
const pending = this.pendingInteractionValues;
|
|
13475
|
+
if (pending && pending.kind === "identity_intake") {
|
|
13476
|
+
const v = pending.values;
|
|
13477
|
+
this.store.getState().mergeIdentity({
|
|
13478
|
+
name: v.name,
|
|
13479
|
+
email: v.email,
|
|
13480
|
+
phone: v.phone
|
|
13481
|
+
});
|
|
13482
|
+
}
|
|
13483
|
+
this.pendingInteractionValues = null;
|
|
13484
|
+
this.setInterrupt(null);
|
|
13485
|
+
}
|
|
13486
|
+
break;
|
|
13319
13487
|
default: break;
|
|
13320
13488
|
}
|
|
13321
13489
|
}
|
|
@@ -13522,6 +13690,7 @@ var SmoothAgentChat = (function(exports) {
|
|
|
13522
13690
|
--sac-bg: ${theme.background};
|
|
13523
13691
|
--sac-primary: ${theme.primary};
|
|
13524
13692
|
--sac-primary-text: ${theme.primaryText};
|
|
13693
|
+
--sac-secondary: ${theme.secondary};
|
|
13525
13694
|
--sac-assistant-bubble: ${theme.assistantBubble};
|
|
13526
13695
|
--sac-assistant-bubble-text: ${theme.assistantBubbleText};
|
|
13527
13696
|
--sac-user-bubble: ${theme.userBubble};
|
|
@@ -13834,6 +14003,47 @@ ${mode === "fullpage" ? `
|
|
|
13834
14003
|
}
|
|
13835
14004
|
@keyframes sac-blink { to { opacity: 0 } }
|
|
13836
14005
|
|
|
14006
|
+
/* Interleaved tool-activity strip (gated by showToolActivity): prose bubbles
|
|
14007
|
+
and inline tool chips stacked in the order the model produced them. */
|
|
14008
|
+
.blocks { display: flex; flex-direction: column; align-items: flex-start; gap: 7px; }
|
|
14009
|
+
.blocks .bubble { align-self: flex-start; }
|
|
14010
|
+
.toolchip {
|
|
14011
|
+
display: inline-flex;
|
|
14012
|
+
align-items: center;
|
|
14013
|
+
gap: 6px;
|
|
14014
|
+
max-width: 100%;
|
|
14015
|
+
padding: 5px 10px;
|
|
14016
|
+
border-radius: 99px;
|
|
14017
|
+
font-size: 12px;
|
|
14018
|
+
line-height: 1.3;
|
|
14019
|
+
color: color-mix(in srgb, var(--sac-text) 78%, transparent);
|
|
14020
|
+
background: color-mix(in srgb, var(--sac-text) 6%, transparent);
|
|
14021
|
+
border: 1px solid color-mix(in srgb, var(--sac-text) 12%, transparent);
|
|
14022
|
+
animation: sac-msg-in .3s var(--sac-ease) both;
|
|
14023
|
+
}
|
|
14024
|
+
.toolchip .ti { display: inline-flex; flex: none; opacity: .8; }
|
|
14025
|
+
.toolchip .ti svg { width: 13px; height: 13px; }
|
|
14026
|
+
.toolchip .tn { font-weight: 600; letter-spacing: .01em; }
|
|
14027
|
+
.toolchip .ts { opacity: .7; }
|
|
14028
|
+
.toolchip .ta {
|
|
14029
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
14030
|
+
font-size: 11px;
|
|
14031
|
+
opacity: .6;
|
|
14032
|
+
overflow: hidden;
|
|
14033
|
+
text-overflow: ellipsis;
|
|
14034
|
+
white-space: nowrap;
|
|
14035
|
+
min-width: 0;
|
|
14036
|
+
}
|
|
14037
|
+
.toolchip.running { border-color: color-mix(in srgb, var(--sac-primary) 45%, transparent); }
|
|
14038
|
+
.toolchip.running .ts { color: var(--sac-primary); opacity: 1; animation: sac-typing 1.3s ease-in-out infinite; }
|
|
14039
|
+
.toolchip.done .ts::before { content: '✓ '; }
|
|
14040
|
+
.toolchip.error {
|
|
14041
|
+
color: var(--sac-secondary);
|
|
14042
|
+
border-color: color-mix(in srgb, var(--sac-secondary) 50%, transparent);
|
|
14043
|
+
background: color-mix(in srgb, var(--sac-secondary) 10%, transparent);
|
|
14044
|
+
}
|
|
14045
|
+
.toolchip.error .ts::before { content: '! '; }
|
|
14046
|
+
|
|
13837
14047
|
/* ─────────────── Rendered markdown (assistant bubbles / snippets) ─────────── */
|
|
13838
14048
|
/* The renderer (markdown.ts) emits a small allowlisted set of tags; these rules
|
|
13839
14049
|
keep them legible inside the tight Aurora-Glass bubble + citation card. */
|
|
@@ -14128,6 +14338,9 @@ ${mode === "fullpage" ? `
|
|
|
14128
14338
|
border-color: color-mix(in srgb, var(--sac-primary) 60%, transparent);
|
|
14129
14339
|
box-shadow: 0 0 0 4px color-mix(in srgb, var(--sac-primary) 14%, transparent);
|
|
14130
14340
|
}
|
|
14341
|
+
.int-form { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
|
|
14342
|
+
.int-form .pc-field input { width: 100%; box-sizing: border-box; }
|
|
14343
|
+
.int-form .int-error { margin-top: 2px; }
|
|
14131
14344
|
.int-btn {
|
|
14132
14345
|
border: 1px solid color-mix(in srgb, var(--sac-border) 80%, transparent);
|
|
14133
14346
|
background: var(--sac-surface-2);
|
|
@@ -14147,8 +14360,14 @@ ${mode === "fullpage" ? `
|
|
|
14147
14360
|
color: var(--sac-primary-text);
|
|
14148
14361
|
box-shadow: 0 6px 14px -6px color-mix(in srgb, var(--sac-primary) 65%, transparent);
|
|
14149
14362
|
}
|
|
14150
|
-
.int-row .int-
|
|
14151
|
-
.int-
|
|
14363
|
+
.int-row .int-form { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
|
|
14364
|
+
.int-form .pc-field input { width: 100%; box-sizing: border-box; }
|
|
14365
|
+
.int-form .int-error { margin-top: 2px; }
|
|
14366
|
+
.int-btn { flex: 1; }
|
|
14367
|
+
.int-row .int-input + .int-form { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
|
|
14368
|
+
.int-form .pc-field input { width: 100%; box-sizing: border-box; }
|
|
14369
|
+
.int-form .int-error { margin-top: 2px; }
|
|
14370
|
+
.int-btn { flex: 0 0 auto; }
|
|
14152
14371
|
.int-error { margin-top: 8px; font-size: 12px; color: #f87171; }
|
|
14153
14372
|
.int-card { position: relative; }
|
|
14154
14373
|
.int-close {
|
|
@@ -14267,7 +14486,8 @@ ${mode === "fullpage" ? `
|
|
|
14267
14486
|
"greeting",
|
|
14268
14487
|
"start-open",
|
|
14269
14488
|
"mode",
|
|
14270
|
-
"hide-branding"
|
|
14489
|
+
"hide-branding",
|
|
14490
|
+
"show-tool-activity"
|
|
14271
14491
|
];
|
|
14272
14492
|
/**
|
|
14273
14493
|
* Inline SVG icons (static, trusted strings — never interpolated with user data).
|
|
@@ -14286,9 +14506,141 @@ ${mode === "fullpage" ? `
|
|
|
14286
14506
|
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
14507
|
/** OTP interrupt — a padlock. */
|
|
14288
14508
|
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>`,
|
|
14509
|
+
/** Identity-intake interrupt — a person. */
|
|
14510
|
+
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
14511
|
/** Tool-confirmation interrupt — a shield. */
|
|
14290
|
-
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
|
|
14512
|
+
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>`,
|
|
14513
|
+
/** Tool-activity chip — a wrench. */
|
|
14514
|
+
tool: `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.7 6.3a3.5 3.5 0 0 0-4.6 4.3l-5 5a1.6 1.6 0 0 0 2.3 2.3l5-5a3.5 3.5 0 0 0 4.3-4.6l-2 2-1.7-.3-.3-1.7 2-2Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg>`
|
|
14291
14515
|
};
|
|
14516
|
+
/**
|
|
14517
|
+
* The identity_intake card: the fields the agent requested (pre-chat form field
|
|
14518
|
+
* pattern — same classes, same phone formatting), per-field server errors, a
|
|
14519
|
+
* submit and a decline affordance. Server-supplied text (reason, labels, error
|
|
14520
|
+
* messages) is set via `textContent` — never innerHTML.
|
|
14521
|
+
*/
|
|
14522
|
+
function buildIdentityIntakeCard(it, ctx) {
|
|
14523
|
+
const form = document.createElement("form");
|
|
14524
|
+
form.className = "int-form";
|
|
14525
|
+
form.noValidate = true;
|
|
14526
|
+
if (it.reason) {
|
|
14527
|
+
const desc = document.createElement("div");
|
|
14528
|
+
desc.className = "int-desc";
|
|
14529
|
+
desc.textContent = it.reason;
|
|
14530
|
+
form.appendChild(desc);
|
|
14531
|
+
}
|
|
14532
|
+
const DEFAULTS = {
|
|
14533
|
+
name: {
|
|
14534
|
+
label: "Name",
|
|
14535
|
+
type: "text",
|
|
14536
|
+
autocomplete: "name"
|
|
14537
|
+
},
|
|
14538
|
+
email: {
|
|
14539
|
+
label: "Email",
|
|
14540
|
+
type: "email",
|
|
14541
|
+
autocomplete: "email"
|
|
14542
|
+
},
|
|
14543
|
+
phone: {
|
|
14544
|
+
label: "Phone",
|
|
14545
|
+
type: "tel",
|
|
14546
|
+
autocomplete: "tel"
|
|
14547
|
+
}
|
|
14548
|
+
};
|
|
14549
|
+
const rawFields = Array.isArray(it.spec.fields) ? it.spec.fields : [];
|
|
14550
|
+
const fields = [];
|
|
14551
|
+
for (const f of rawFields) {
|
|
14552
|
+
if (!f || typeof f !== "object") continue;
|
|
14553
|
+
const o = f;
|
|
14554
|
+
const key = typeof o.key === "string" ? o.key : "";
|
|
14555
|
+
if (key !== "name" && key !== "email" && key !== "phone") continue;
|
|
14556
|
+
fields.push({
|
|
14557
|
+
key,
|
|
14558
|
+
required: o.required === true,
|
|
14559
|
+
label: typeof o.label === "string" ? o.label : void 0
|
|
14560
|
+
});
|
|
14561
|
+
}
|
|
14562
|
+
if (fields.length === 0) fields.push({
|
|
14563
|
+
key: "email",
|
|
14564
|
+
required: true
|
|
14565
|
+
});
|
|
14566
|
+
for (const f of fields) {
|
|
14567
|
+
const d = DEFAULTS[f.key];
|
|
14568
|
+
const label = document.createElement("label");
|
|
14569
|
+
label.className = "pc-field";
|
|
14570
|
+
const caption = document.createElement("span");
|
|
14571
|
+
caption.textContent = f.label ?? d.label;
|
|
14572
|
+
const input = document.createElement("input");
|
|
14573
|
+
input.name = f.key;
|
|
14574
|
+
input.type = d.type;
|
|
14575
|
+
input.setAttribute("autocomplete", d.autocomplete);
|
|
14576
|
+
input.required = f.required;
|
|
14577
|
+
const prefill = ctx.prefill[f.key];
|
|
14578
|
+
if (prefill) input.value = prefill;
|
|
14579
|
+
label.append(caption, input);
|
|
14580
|
+
if (f.key === "phone") {
|
|
14581
|
+
const hint = document.createElement("span");
|
|
14582
|
+
hint.className = "pc-hint";
|
|
14583
|
+
hint.setAttribute("aria-live", "polite");
|
|
14584
|
+
label.appendChild(hint);
|
|
14585
|
+
}
|
|
14586
|
+
const serverError = it.errors?.find((e) => e.field === f.key);
|
|
14587
|
+
if (serverError) {
|
|
14588
|
+
label.classList.add("invalid");
|
|
14589
|
+
const err = document.createElement("span");
|
|
14590
|
+
err.className = "int-error";
|
|
14591
|
+
err.textContent = serverError.message;
|
|
14592
|
+
label.appendChild(err);
|
|
14593
|
+
}
|
|
14594
|
+
form.appendChild(label);
|
|
14595
|
+
}
|
|
14596
|
+
const row = document.createElement("div");
|
|
14597
|
+
row.className = "int-row";
|
|
14598
|
+
const decline = document.createElement("button");
|
|
14599
|
+
decline.className = "int-btn";
|
|
14600
|
+
decline.type = "button";
|
|
14601
|
+
decline.textContent = "No thanks";
|
|
14602
|
+
decline.addEventListener("click", () => ctx.decline());
|
|
14603
|
+
const share = document.createElement("button");
|
|
14604
|
+
share.className = "int-btn primary";
|
|
14605
|
+
share.type = "submit";
|
|
14606
|
+
share.textContent = "Share details";
|
|
14607
|
+
row.append(decline, share);
|
|
14608
|
+
form.appendChild(row);
|
|
14609
|
+
form.addEventListener("submit", (ev) => {
|
|
14610
|
+
ev.preventDefault();
|
|
14611
|
+
if (!form.reportValidity()) return;
|
|
14612
|
+
const data = new FormData(form);
|
|
14613
|
+
const val = (k) => data.get(k)?.trim() || void 0;
|
|
14614
|
+
const rawPhone = val("phone");
|
|
14615
|
+
const phoneInput = form.querySelector("input[name=\"phone\"]");
|
|
14616
|
+
if (rawPhone && phoneInput && !isValidPhoneNumber(rawPhone, PHONE_DEFAULT_REGION)) {
|
|
14617
|
+
const field = phoneInput.closest(".pc-field");
|
|
14618
|
+
field?.classList.add("invalid");
|
|
14619
|
+
const hint = field?.querySelector(".pc-hint");
|
|
14620
|
+
if (hint) hint.textContent = "Enter a valid phone number";
|
|
14621
|
+
if (phoneInput.hasAttribute("required")) {
|
|
14622
|
+
phoneInput.focus();
|
|
14623
|
+
return;
|
|
14624
|
+
}
|
|
14625
|
+
}
|
|
14626
|
+
const phone = rawPhone ? phoneToE164(rawPhone) ?? rawPhone : void 0;
|
|
14627
|
+
ctx.submit({
|
|
14628
|
+
name: val("name"),
|
|
14629
|
+
email: val("email"),
|
|
14630
|
+
phone
|
|
14631
|
+
});
|
|
14632
|
+
});
|
|
14633
|
+
ctx.wirePhoneField(form);
|
|
14634
|
+
queueMicrotask(() => form.querySelector("input")?.focus());
|
|
14635
|
+
return form;
|
|
14636
|
+
}
|
|
14637
|
+
/** Kind → card. See the registry doc above. */
|
|
14638
|
+
const INTERACTION_CARDS = { identity_intake: {
|
|
14639
|
+
capability: "identity_form",
|
|
14640
|
+
title: "Share your details",
|
|
14641
|
+
icon: ICON.user,
|
|
14642
|
+
build: buildIdentityIntakeCard
|
|
14643
|
+
} };
|
|
14292
14644
|
var SmoothAgentChatElement = class extends HTMLElement {
|
|
14293
14645
|
static get observedAttributes() {
|
|
14294
14646
|
return OBSERVED;
|
|
@@ -14325,6 +14677,7 @@ ${mode === "fullpage" ? `
|
|
|
14325
14677
|
_defineProperty(this, "streamTarget", "");
|
|
14326
14678
|
_defineProperty(this, "displayedLength", 0);
|
|
14327
14679
|
_defineProperty(this, "rafId", 0);
|
|
14680
|
+
_defineProperty(this, "prevBlockSig", "");
|
|
14328
14681
|
this.root = this.attachShadow({ mode: "open" });
|
|
14329
14682
|
}
|
|
14330
14683
|
connectedCallback() {
|
|
@@ -14396,6 +14749,7 @@ ${mode === "fullpage" ? `
|
|
|
14396
14749
|
collectConsent: this.overrides.collectConsent,
|
|
14397
14750
|
allowChatRestore: this.overrides.allowChatRestore,
|
|
14398
14751
|
allowAnonymous: this.overrides.allowAnonymous,
|
|
14752
|
+
showToolActivity: this.overrides.showToolActivity ?? this.hasAttribute("show-tool-activity"),
|
|
14399
14753
|
theme
|
|
14400
14754
|
};
|
|
14401
14755
|
}
|
|
@@ -14571,19 +14925,31 @@ ${mode === "fullpage" ? `
|
|
|
14571
14925
|
head.className = "int-head";
|
|
14572
14926
|
const ico = document.createElement("span");
|
|
14573
14927
|
ico.className = "int-ico";
|
|
14574
|
-
|
|
14928
|
+
const card_meta = it.kind === "interaction" ? INTERACTION_CARDS[it.interactionKind] : void 0;
|
|
14929
|
+
if (it.kind === "interaction" && !card_meta) {
|
|
14930
|
+
this.controller?.declineInteraction();
|
|
14931
|
+
el.classList.add("hidden");
|
|
14932
|
+
return;
|
|
14933
|
+
}
|
|
14934
|
+
ico.innerHTML = it.kind === "otp" ? ICON.lock : it.kind === "interaction" ? card_meta?.icon ?? ICON.user : ICON.shield;
|
|
14575
14935
|
const title = document.createElement("span");
|
|
14576
14936
|
title.className = "int-title";
|
|
14577
|
-
title.textContent = it.kind === "otp" ? "Verification required" : "Confirm this action";
|
|
14937
|
+
title.textContent = it.kind === "otp" ? "Verification required" : it.kind === "interaction" ? card_meta?.title ?? "One more thing" : "Confirm this action";
|
|
14578
14938
|
head.append(ico, title);
|
|
14579
14939
|
card.appendChild(head);
|
|
14580
|
-
if (it.actionDescription) {
|
|
14940
|
+
if (it.kind !== "interaction" && it.actionDescription) {
|
|
14581
14941
|
const desc = document.createElement("div");
|
|
14582
14942
|
desc.className = "int-desc";
|
|
14583
14943
|
desc.textContent = it.actionDescription;
|
|
14584
14944
|
card.appendChild(desc);
|
|
14585
14945
|
}
|
|
14586
|
-
if (it.kind === "
|
|
14946
|
+
if (it.kind === "interaction") card.appendChild(card_meta.build(it, {
|
|
14947
|
+
submit: (values) => this.controller?.submitInteraction(values),
|
|
14948
|
+
decline: () => this.controller?.declineInteraction(),
|
|
14949
|
+
prefill: this.controller?.getStore().getState().identity ?? {},
|
|
14950
|
+
wirePhoneField: (form) => this.wirePhoneField(form)
|
|
14951
|
+
}));
|
|
14952
|
+
else if (it.kind === "otp") {
|
|
14587
14953
|
if (it.sent?.maskedDestination) {
|
|
14588
14954
|
const sent = document.createElement("div");
|
|
14589
14955
|
sent.className = "int-sent";
|
|
@@ -14923,15 +15289,38 @@ ${mode === "fullpage" ? `
|
|
|
14923
15289
|
const prev = this.messages;
|
|
14924
15290
|
const last = messages[messages.length - 1];
|
|
14925
15291
|
const prevLast = prev[prev.length - 1];
|
|
14926
|
-
const structural = messages.length !== prev.length || !last || !prevLast || last.id !== prevLast.id || last.role !== prevLast.role || last.streaming !== prevLast.streaming || !!last.streaming && !prevLast.text && !!last.text;
|
|
15292
|
+
const structural = messages.length !== prev.length || !last || !prevLast || last.id !== prevLast.id || last.role !== prevLast.role || last.streaming !== prevLast.streaming || !!last.streaming && !prevLast.text && !!last.text || this.blockSig(last) !== this.prevBlockSig;
|
|
14927
15293
|
this.messages = messages;
|
|
14928
|
-
|
|
14929
|
-
|
|
15294
|
+
this.prevBlockSig = this.blockSig(last);
|
|
15295
|
+
if (!structural && last && last.streaming && this.tailKey(last) === this.streamMsgId) {
|
|
15296
|
+
this.streamTarget = this.tailText(last);
|
|
14930
15297
|
this.ensureRevealLoop();
|
|
14931
15298
|
return;
|
|
14932
15299
|
}
|
|
14933
15300
|
this.renderMessages();
|
|
14934
15301
|
}
|
|
15302
|
+
/** True when an assistant message's turn invoked at least one tool. */
|
|
15303
|
+
hasToolBlocks(m) {
|
|
15304
|
+
return !!m?.blocks?.some((b) => b.kind === "tool");
|
|
15305
|
+
}
|
|
15306
|
+
/** Signature that changes only when a tool chip is added or resolved (text growth is 'x'). */
|
|
15307
|
+
blockSig(m) {
|
|
15308
|
+
if (!m?.blocks) return "";
|
|
15309
|
+
return m.blocks.map((b) => b.kind === "tool" ? `t:${b.tool.id}:${b.tool.done ? 1 : 0}` : "x").join("|");
|
|
15310
|
+
}
|
|
15311
|
+
/** The live (last) text block for a tool turn, else the whole message text. */
|
|
15312
|
+
tailText(m) {
|
|
15313
|
+
if (this.hasToolBlocks(m) && m.blocks) {
|
|
15314
|
+
const last = m.blocks[m.blocks.length - 1];
|
|
15315
|
+
return last?.kind === "text" ? last.text : "";
|
|
15316
|
+
}
|
|
15317
|
+
return m.text;
|
|
15318
|
+
}
|
|
15319
|
+
/** Reveal-binding key — composite for a tool-turn tail (so a new trailing block rebinds), else msg id. */
|
|
15320
|
+
tailKey(m) {
|
|
15321
|
+
if (this.hasToolBlocks(m) && m.blocks) return `${m.id}#${m.blocks.length - 1}`;
|
|
15322
|
+
return m.id;
|
|
15323
|
+
}
|
|
14935
15324
|
renderMessages() {
|
|
14936
15325
|
if (!this.messagesEl) return;
|
|
14937
15326
|
this.resetReveal();
|
|
@@ -14952,6 +15341,11 @@ ${mode === "fullpage" ? `
|
|
|
14952
15341
|
this.messagesEl.appendChild(chips);
|
|
14953
15342
|
}
|
|
14954
15343
|
for (const msg of this.messages) {
|
|
15344
|
+
if (msg.role === "assistant" && this.hasToolBlocks(msg)) {
|
|
15345
|
+
this.messagesEl.appendChild(this.buildRow("assistant", this.renderAssistantBlocks(msg)));
|
|
15346
|
+
if (!msg.streaming && msg.citations && msg.citations.length > 0) this.messagesEl.appendChild(this.renderSources(msg.citations));
|
|
15347
|
+
continue;
|
|
15348
|
+
}
|
|
14955
15349
|
const bubble = document.createElement("div");
|
|
14956
15350
|
bubble.className = `bubble ${msg.role}`;
|
|
14957
15351
|
if (msg.role === "assistant" && msg.streaming && !msg.text) {
|
|
@@ -15009,19 +15403,77 @@ ${mode === "fullpage" ? `
|
|
|
15009
15403
|
* doesn't restart the reveal from zero), then resumes the loop.
|
|
15010
15404
|
*/
|
|
15011
15405
|
bindReveal(msg, bubble) {
|
|
15012
|
-
const
|
|
15406
|
+
const key = this.tailKey(msg);
|
|
15407
|
+
const target = this.tailText(msg);
|
|
15408
|
+
const carryOver = key === this.streamMsgId ? Math.min(this.displayedLength, target.length) : 0;
|
|
15013
15409
|
this.streamBubbleEl = bubble;
|
|
15014
|
-
this.streamMsgId =
|
|
15015
|
-
this.streamTarget =
|
|
15410
|
+
this.streamMsgId = key;
|
|
15411
|
+
this.streamTarget = target;
|
|
15016
15412
|
this.displayedLength = carryOver;
|
|
15017
15413
|
if (this.prefersReducedMotion()) {
|
|
15018
|
-
this.displayedLength =
|
|
15019
|
-
bubble.textContent =
|
|
15414
|
+
this.displayedLength = target.length;
|
|
15415
|
+
bubble.textContent = target;
|
|
15020
15416
|
return;
|
|
15021
15417
|
}
|
|
15022
|
-
bubble.textContent =
|
|
15418
|
+
bubble.textContent = target.slice(0, this.displayedLength);
|
|
15023
15419
|
this.ensureRevealLoop();
|
|
15024
15420
|
}
|
|
15421
|
+
/**
|
|
15422
|
+
* Render a tool-activity assistant turn as an ordered strip: prose bubbles and
|
|
15423
|
+
* inline tool chips in the order the model produced them (mirrors the daemon
|
|
15424
|
+
* SPA's `blocks`). The live trailing text block (while streaming) binds to the
|
|
15425
|
+
* rAF reveal; earlier/finalized text blocks render as sanitized markdown.
|
|
15426
|
+
*/
|
|
15427
|
+
renderAssistantBlocks(msg) {
|
|
15428
|
+
const wrap = document.createElement("div");
|
|
15429
|
+
wrap.className = "blocks";
|
|
15430
|
+
const blocks = msg.blocks ?? [];
|
|
15431
|
+
const lastIdx = blocks.length - 1;
|
|
15432
|
+
blocks.forEach((block, i) => {
|
|
15433
|
+
if (block.kind === "tool") {
|
|
15434
|
+
wrap.appendChild(this.buildToolChip(block.tool));
|
|
15435
|
+
return;
|
|
15436
|
+
}
|
|
15437
|
+
const bubble = document.createElement("div");
|
|
15438
|
+
bubble.className = "bubble assistant";
|
|
15439
|
+
if (msg.streaming && i === lastIdx) {
|
|
15440
|
+
bubble.classList.add("cursor");
|
|
15441
|
+
this.bindReveal(msg, bubble);
|
|
15442
|
+
} else {
|
|
15443
|
+
bubble.classList.add("md");
|
|
15444
|
+
bubble.innerHTML = renderMarkdown(block.text);
|
|
15445
|
+
}
|
|
15446
|
+
wrap.appendChild(bubble);
|
|
15447
|
+
});
|
|
15448
|
+
return wrap;
|
|
15449
|
+
}
|
|
15450
|
+
/**
|
|
15451
|
+
* A single tool-activity chip: icon + tool name + status (running… / done / error),
|
|
15452
|
+
* with a truncated args preview. Tool name/args are set via `textContent` so a
|
|
15453
|
+
* tool payload can never inject markup.
|
|
15454
|
+
*/
|
|
15455
|
+
buildToolChip(tool) {
|
|
15456
|
+
const chip = document.createElement("div");
|
|
15457
|
+
chip.className = `toolchip ${tool.done ? tool.isError ? "error" : "done" : "running"}`;
|
|
15458
|
+
chip.setAttribute("part", "tool-chip");
|
|
15459
|
+
const icon = document.createElement("span");
|
|
15460
|
+
icon.className = "ti";
|
|
15461
|
+
icon.innerHTML = ICON.tool;
|
|
15462
|
+
const name = document.createElement("span");
|
|
15463
|
+
name.className = "tn";
|
|
15464
|
+
name.textContent = tool.name || "tool";
|
|
15465
|
+
const status = document.createElement("span");
|
|
15466
|
+
status.className = "ts";
|
|
15467
|
+
status.textContent = tool.done ? tool.isError ? "error" : "done" : "running…";
|
|
15468
|
+
chip.append(icon, name, status);
|
|
15469
|
+
if (tool.args && tool.args !== "{}" && tool.args !== "\"\"") {
|
|
15470
|
+
const args = document.createElement("span");
|
|
15471
|
+
args.className = "ta";
|
|
15472
|
+
args.textContent = tool.args.length > 80 ? `${tool.args.slice(0, 80)}…` : tool.args;
|
|
15473
|
+
chip.appendChild(args);
|
|
15474
|
+
}
|
|
15475
|
+
return chip;
|
|
15476
|
+
}
|
|
15025
15477
|
/** Start the rAF loop if it isn't already running. */
|
|
15026
15478
|
ensureRevealLoop() {
|
|
15027
15479
|
if (this.prefersReducedMotion() || typeof requestAnimationFrame !== "function") {
|