@smooai/smooth-operator 1.11.4 → 1.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/dist/widget/chat-widget.iife.js +135 -2
- package/dist/widget/chat-widget.iife.js.map +1 -1
- package/dist/widget/conversation.d.ts +69 -1
- package/dist/widget/conversation.d.ts.map +1 -1
- package/dist/widget/conversation.js +67 -2
- package/dist/widget/conversation.js.map +1 -1
- package/dist/widget/element.d.ts +8 -0
- package/dist/widget/element.d.ts.map +1 -1
- package/dist/widget/element.js +43 -0
- package/dist/widget/element.js.map +1 -1
- package/dist/widget/styles.d.ts.map +1 -1
- package/dist/widget/styles.js +29 -0
- package/dist/widget/styles.js.map +1 -1
- package/package.json +1 -1
- package/src/widget/conversation.ts +95 -3
- package/src/widget/element.ts +49 -1
- package/src/widget/styles.ts +29 -0
|
@@ -593,9 +593,18 @@ var SmoothAgentChat = (function(exports) {
|
|
|
593
593
|
messages = [];
|
|
594
594
|
status = "idle";
|
|
595
595
|
seq = 0;
|
|
596
|
-
|
|
596
|
+
/**
|
|
597
|
+
* requestId → resolver for a pending button prompt. The turn loop parks on
|
|
598
|
+
* the promise; {@link answerPrompt} (called by the view on a button click)
|
|
599
|
+
* resolves it and the loop resumes consuming the (now un-paused) stream.
|
|
600
|
+
*/
|
|
601
|
+
pendingPrompts = /* @__PURE__ */ new Map();
|
|
602
|
+
/** Extra client options — a test seam for injecting a mock transport. */
|
|
603
|
+
clientOptions;
|
|
604
|
+
constructor(config, events, clientOptions) {
|
|
597
605
|
this.config = config;
|
|
598
606
|
this.events = events;
|
|
607
|
+
this.clientOptions = clientOptions;
|
|
599
608
|
}
|
|
600
609
|
get connectionStatus() {
|
|
601
610
|
return this.status;
|
|
@@ -616,7 +625,10 @@ var SmoothAgentChat = (function(exports) {
|
|
|
616
625
|
if (this.status === "connecting" || this.status === "ready") return;
|
|
617
626
|
this.setStatus("connecting");
|
|
618
627
|
try {
|
|
619
|
-
this.client = new SmoothAgentClient({
|
|
628
|
+
this.client = new SmoothAgentClient({
|
|
629
|
+
url: this.config.endpoint,
|
|
630
|
+
...this.clientOptions
|
|
631
|
+
});
|
|
620
632
|
await this.client.connect();
|
|
621
633
|
const session = await this.client.createConversationSession({
|
|
622
634
|
agentId: this.config.agentId,
|
|
@@ -665,6 +677,25 @@ var SmoothAgentChat = (function(exports) {
|
|
|
665
677
|
assistant.text += token;
|
|
666
678
|
this.emitMessages();
|
|
667
679
|
}
|
|
680
|
+
} else if (event.type === "write_confirmation_required") {
|
|
681
|
+
const description = event.data?.data?.actionDescription ?? "Approve this action?";
|
|
682
|
+
const approved = await this.awaitPromptChoice(turn.requestId, {
|
|
683
|
+
requestId: turn.requestId,
|
|
684
|
+
kind: "confirm",
|
|
685
|
+
text: String(description),
|
|
686
|
+
options: [{
|
|
687
|
+
label: "Yes",
|
|
688
|
+
value: true
|
|
689
|
+
}, {
|
|
690
|
+
label: "No",
|
|
691
|
+
value: false
|
|
692
|
+
}]
|
|
693
|
+
});
|
|
694
|
+
this.client?.confirmToolAction({
|
|
695
|
+
sessionId: this.sessionId,
|
|
696
|
+
requestId: turn.requestId,
|
|
697
|
+
approved: approved === true
|
|
698
|
+
});
|
|
668
699
|
}
|
|
669
700
|
const inner = (await turn).data?.data;
|
|
670
701
|
const finalText = extractFinalText(inner?.response);
|
|
@@ -682,8 +713,43 @@ var SmoothAgentChat = (function(exports) {
|
|
|
682
713
|
this.setStatus("error", err instanceof Error ? err.message : String(err));
|
|
683
714
|
}
|
|
684
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* Attach a pending `prompt` to the current assistant bubble and return a
|
|
718
|
+
* promise that resolves when the user clicks a button (via
|
|
719
|
+
* {@link answerPrompt}). The bubble carrying the prompt is the last
|
|
720
|
+
* assistant message.
|
|
721
|
+
*/
|
|
722
|
+
awaitPromptChoice(requestId, prompt) {
|
|
723
|
+
const last = this.messages[this.messages.length - 1];
|
|
724
|
+
if (last && last.role === "assistant") {
|
|
725
|
+
last.prompt = prompt;
|
|
726
|
+
last.streaming = false;
|
|
727
|
+
}
|
|
728
|
+
this.emitMessages();
|
|
729
|
+
return new Promise((resolve) => {
|
|
730
|
+
this.pendingPrompts.set(requestId, resolve);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Resolve a pending button prompt — called by the view when the user clicks
|
|
735
|
+
* an option. Marks the prompt answered (buttons disable, choice sticks) and
|
|
736
|
+
* unblocks the turn loop. No-op if the prompt was already answered.
|
|
737
|
+
*/
|
|
738
|
+
answerPrompt(requestId, value) {
|
|
739
|
+
const resolve = this.pendingPrompts.get(requestId);
|
|
740
|
+
if (!resolve) return;
|
|
741
|
+
this.pendingPrompts.delete(requestId);
|
|
742
|
+
const msg = this.messages.find((m) => m.prompt?.requestId === requestId);
|
|
743
|
+
if (msg?.prompt) msg.prompt.answered = msg.prompt.options.find((o) => o.value === value)?.label ?? String(value);
|
|
744
|
+
this.emitMessages();
|
|
745
|
+
resolve(value);
|
|
746
|
+
}
|
|
685
747
|
/** Tear down the underlying client. */
|
|
686
748
|
disconnect() {
|
|
749
|
+
for (const [requestId, resolve] of this.pendingPrompts) {
|
|
750
|
+
this.pendingPrompts.delete(requestId);
|
|
751
|
+
resolve(false);
|
|
752
|
+
}
|
|
687
753
|
this.client?.disconnect("widget closed");
|
|
688
754
|
this.client = null;
|
|
689
755
|
this.sessionId = null;
|
|
@@ -866,6 +932,35 @@ var SmoothAgentChat = (function(exports) {
|
|
|
866
932
|
|
|
867
933
|
/* Sources panel — rendered under an assistant bubble whose terminal
|
|
868
934
|
eventual_response carried citations. */
|
|
935
|
+
.prompt {
|
|
936
|
+
align-self: flex-start;
|
|
937
|
+
max-width: 80%;
|
|
938
|
+
margin-top: -2px;
|
|
939
|
+
display: flex;
|
|
940
|
+
flex-direction: column;
|
|
941
|
+
gap: 8px;
|
|
942
|
+
}
|
|
943
|
+
.panel.fullpage .prompt { max-width: 100%; }
|
|
944
|
+
.prompt-text { font-size: 13.5px; color: var(--sac-text); opacity: 0.9; }
|
|
945
|
+
.prompt-buttons { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
946
|
+
.prompt-button {
|
|
947
|
+
cursor: pointer;
|
|
948
|
+
border: 1px solid var(--sac-border);
|
|
949
|
+
background: var(--sac-bg);
|
|
950
|
+
color: var(--sac-text);
|
|
951
|
+
border-radius: 999px;
|
|
952
|
+
padding: 6px 16px;
|
|
953
|
+
font-size: 13px;
|
|
954
|
+
font-weight: 600;
|
|
955
|
+
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
956
|
+
}
|
|
957
|
+
.prompt-button:hover {
|
|
958
|
+
background: var(--sac-primary);
|
|
959
|
+
color: var(--sac-primary-text);
|
|
960
|
+
border-color: var(--sac-primary);
|
|
961
|
+
}
|
|
962
|
+
.prompt-answered { font-size: 12.5px; opacity: 0.7; font-style: italic; }
|
|
963
|
+
|
|
869
964
|
.sources {
|
|
870
965
|
align-self: flex-start;
|
|
871
966
|
max-width: 80%;
|
|
@@ -1174,11 +1269,49 @@ var SmoothAgentChat = (function(exports) {
|
|
|
1174
1269
|
el.textContent = msg.text;
|
|
1175
1270
|
} else el.textContent = msg.text;
|
|
1176
1271
|
this.messagesEl.appendChild(el);
|
|
1272
|
+
if (msg.prompt) this.messagesEl.appendChild(this.renderPrompt(msg.prompt));
|
|
1177
1273
|
if (msg.role === "assistant" && !msg.streaming && msg.citations && msg.citations.length > 0) this.messagesEl.appendChild(this.renderSources(msg.citations));
|
|
1178
1274
|
}
|
|
1179
1275
|
this.messagesEl.scrollTop = this.messagesEl.scrollHeight;
|
|
1180
1276
|
}
|
|
1181
1277
|
/**
|
|
1278
|
+
* Render a chat-native button prompt (SEP `ui/confirm` / `ui/select`). Each
|
|
1279
|
+
* option is a button; clicking one resumes the paused turn via
|
|
1280
|
+
* {@link ConversationController.answerPrompt}. Once answered the buttons
|
|
1281
|
+
* disable and the chosen label is shown, so the record stays in the
|
|
1282
|
+
* transcript. Built with DOM APIs (no innerHTML) — prompt text is untrusted.
|
|
1283
|
+
*/
|
|
1284
|
+
renderPrompt(prompt) {
|
|
1285
|
+
const wrap = document.createElement("div");
|
|
1286
|
+
wrap.className = "prompt";
|
|
1287
|
+
wrap.setAttribute("part", "prompt");
|
|
1288
|
+
const text = document.createElement("div");
|
|
1289
|
+
text.className = "prompt-text";
|
|
1290
|
+
text.textContent = prompt.text;
|
|
1291
|
+
wrap.appendChild(text);
|
|
1292
|
+
if (prompt.answered) {
|
|
1293
|
+
const chosen = document.createElement("div");
|
|
1294
|
+
chosen.className = "prompt-answered";
|
|
1295
|
+
chosen.textContent = `You chose: ${prompt.answered}`;
|
|
1296
|
+
wrap.appendChild(chosen);
|
|
1297
|
+
return wrap;
|
|
1298
|
+
}
|
|
1299
|
+
const buttons = document.createElement("div");
|
|
1300
|
+
buttons.className = "prompt-buttons";
|
|
1301
|
+
for (const opt of prompt.options) {
|
|
1302
|
+
const btn = document.createElement("button");
|
|
1303
|
+
btn.className = "prompt-button";
|
|
1304
|
+
btn.type = "button";
|
|
1305
|
+
btn.textContent = opt.label;
|
|
1306
|
+
btn.addEventListener("click", () => {
|
|
1307
|
+
this.controller?.answerPrompt(prompt.requestId, opt.value);
|
|
1308
|
+
});
|
|
1309
|
+
buttons.appendChild(btn);
|
|
1310
|
+
}
|
|
1311
|
+
wrap.appendChild(buttons);
|
|
1312
|
+
return wrap;
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1182
1315
|
* Build the collapsible "Sources (N)" block for an assistant message's
|
|
1183
1316
|
* citations. Each source renders its `title` (linked to `citation.url` when
|
|
1184
1317
|
* present — `target=_blank rel=noopener` — plain text otherwise) plus the
|