@syntrologie/adapt-chatbot 2.28.0 → 2.29.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/AdaptiveChatBar.composer.test.d.ts +2 -0
- package/dist/AdaptiveChatBar.composer.test.d.ts.map +1 -0
- package/dist/AdaptiveChatBar.d.ts +140 -4
- package/dist/AdaptiveChatBar.d.ts.map +1 -1
- package/dist/AdaptiveChatBar.js +4 -3
- package/dist/AdaptiveChatBarMountable.d.ts +47 -0
- package/dist/AdaptiveChatBarMountable.d.ts.map +1 -1
- package/dist/AdaptiveChatTrail.anchor-scroll.test.d.ts +2 -0
- package/dist/AdaptiveChatTrail.anchor-scroll.test.d.ts.map +1 -0
- package/dist/AdaptiveChatTrail.d.ts +397 -5
- package/dist/AdaptiveChatTrail.d.ts.map +1 -1
- package/dist/AdaptiveChatTrail.js +6 -4
- package/dist/AdaptiveChatTrail.test.d.ts +2 -0
- package/dist/AdaptiveChatTrail.test.d.ts.map +1 -0
- package/dist/AdaptiveChipsStrip.d.ts +17 -11
- package/dist/AdaptiveChipsStrip.d.ts.map +1 -1
- package/dist/AdaptiveChipsStrip.js +2 -2
- package/dist/ChatAssistantLit.d.ts.map +1 -1
- package/dist/ChatAssistantLit.js +4 -3
- package/dist/ChatSession.d.ts +66 -5
- package/dist/ChatSession.d.ts.map +1 -1
- package/dist/ChatTransport.d.ts +107 -0
- package/dist/ChatTransport.d.ts.map +1 -1
- package/dist/InlineRegistryWidgetMount.test.d.ts +2 -0
- package/dist/InlineRegistryWidgetMount.test.d.ts.map +1 -0
- package/dist/InlineWidgetLiveMount.test.d.ts +2 -0
- package/dist/InlineWidgetLiveMount.test.d.ts.map +1 -0
- package/dist/NavLinkMountable.d.ts.map +1 -1
- package/dist/TextAnswerMountable.test.d.ts +2 -0
- package/dist/TextAnswerMountable.test.d.ts.map +1 -0
- package/dist/Turnstile.d.ts.map +1 -1
- package/dist/chunk-A7RBZLIX.js +1678 -0
- package/dist/chunk-A7RBZLIX.js.map +7 -0
- package/dist/{chunk-435KJD27.js → chunk-EACGDSJI.js} +46 -1
- package/dist/chunk-EACGDSJI.js.map +7 -0
- package/dist/chunk-EWPPVPJ4.js +14 -0
- package/dist/chunk-EWPPVPJ4.js.map +7 -0
- package/dist/chunk-FXHYE6FY.js +632 -0
- package/dist/chunk-FXHYE6FY.js.map +7 -0
- package/dist/{chunk-AUER7ZCK.js → chunk-OIGVZGSX.js} +11 -4
- package/dist/chunk-OIGVZGSX.js.map +7 -0
- package/dist/{chunk-UC4XU6GH.js → chunk-VZIUXXAM.js} +761 -52
- package/dist/chunk-VZIUXXAM.js.map +7 -0
- package/dist/{chunk-KUO67E2W.js → chunk-ZXV4GXMO.js} +715 -227
- package/dist/chunk-ZXV4GXMO.js.map +7 -0
- package/dist/elements/ActionHandler.d.ts +1 -1
- package/dist/elements/ActionHandler.d.ts.map +1 -1
- package/dist/elements/ElementInstanceStore.d.ts +62 -0
- package/dist/elements/ElementInstanceStore.d.ts.map +1 -1
- package/dist/elements/TileHandler.d.ts.map +1 -1
- package/dist/elements/TileHandler.test.d.ts +2 -0
- package/dist/elements/TileHandler.test.d.ts.map +1 -0
- package/dist/elements/__tests__/ElementInstanceStore.recall.test.d.ts +2 -0
- package/dist/elements/__tests__/ElementInstanceStore.recall.test.d.ts.map +1 -0
- package/dist/elements/fetcher.d.ts +3 -3
- package/dist/elements/fetcher.d.ts.map +1 -1
- package/dist/elements/index.d.ts +1 -0
- package/dist/elements/index.d.ts.map +1 -1
- package/dist/elements/store-singleton.d.ts +4 -0
- package/dist/elements/store-singleton.d.ts.map +1 -0
- package/dist/elements/types.d.ts +4 -0
- package/dist/elements/types.d.ts.map +1 -1
- package/dist/observer/__tests__/bus-translate.test.d.ts +16 -0
- package/dist/observer/__tests__/bus-translate.test.d.ts.map +1 -0
- package/dist/observer/__tests__/lifecycle.test.d.ts +2 -0
- package/dist/observer/__tests__/lifecycle.test.d.ts.map +1 -0
- package/dist/observer/__tests__/observer-enrich.test.d.ts +10 -0
- package/dist/observer/__tests__/observer-enrich.test.d.ts.map +1 -0
- package/dist/observer/allowlist.d.ts +5 -7
- package/dist/observer/allowlist.d.ts.map +1 -1
- package/dist/observer/bus-translate.d.ts +33 -0
- package/dist/observer/bus-translate.d.ts.map +1 -0
- package/dist/observer/index.d.ts +10 -1
- package/dist/observer/index.d.ts.map +1 -1
- package/dist/observer/queue.d.ts +6 -0
- package/dist/observer/queue.d.ts.map +1 -1
- package/dist/observer/transport.d.ts +1 -0
- package/dist/observer/transport.d.ts.map +1 -1
- package/dist/personas.d.ts +30 -0
- package/dist/personas.d.ts.map +1 -0
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +856 -129
- package/dist/runtime.js.map +4 -4
- package/dist/schema.d.ts +363 -67
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +176 -3
- package/dist/schema.js.map +2 -2
- package/dist/test/setup.d.ts +1 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-435KJD27.js.map +0 -7
- package/dist/chunk-AUER7ZCK.js.map +0 -7
- package/dist/chunk-DOMEUJR7.js +0 -382
- package/dist/chunk-DOMEUJR7.js.map +0 -7
- package/dist/chunk-KUO67E2W.js.map +0 -7
- package/dist/chunk-QELVKBQV.js +0 -214
- package/dist/chunk-QELVKBQV.js.map +0 -7
- package/dist/chunk-UC4XU6GH.js.map +0 -7
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getElementInstanceStore
|
|
3
|
+
} from "./chunk-EWPPVPJ4.js";
|
|
4
|
+
import {
|
|
5
|
+
createDictation,
|
|
6
|
+
isDictationSupported
|
|
7
|
+
} from "./chunk-VZIUXXAM.js";
|
|
8
|
+
|
|
9
|
+
// src/AdaptiveChatBar.ts
|
|
10
|
+
import { html, LitElement, nothing, svg } from "lit";
|
|
11
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
12
|
+
|
|
13
|
+
// src/personas.ts
|
|
14
|
+
var STORAGE_KEY = "syntro:persona";
|
|
15
|
+
function pickPersona(roster) {
|
|
16
|
+
if (!Array.isArray(roster) || roster.length === 0) return null;
|
|
17
|
+
let storedId = null;
|
|
18
|
+
try {
|
|
19
|
+
storedId = localStorage.getItem(STORAGE_KEY);
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
const stored = storedId ? roster.find((p) => p.id === storedId) : void 0;
|
|
23
|
+
if (stored) return stored;
|
|
24
|
+
const pick = roster[Math.floor(Math.random() * roster.length)];
|
|
25
|
+
try {
|
|
26
|
+
localStorage.setItem(STORAGE_KEY, pick.id);
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return pick;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/AdaptiveChatBar.ts
|
|
33
|
+
var DEFAULT_PLACEHOLDER = "Ask, find, or navigate\u2026";
|
|
34
|
+
var DEFAULT_ASSISTANT_NAME = "Assistant";
|
|
35
|
+
var AdaptiveChatBar = class extends LitElement {
|
|
36
|
+
constructor() {
|
|
37
|
+
super(...arguments);
|
|
38
|
+
this.messages = [];
|
|
39
|
+
this.maximized = false;
|
|
40
|
+
this.tileReceipts = [];
|
|
41
|
+
/** Widgets rendered inline in the trail (reco carousel), sourced from
|
|
42
|
+
* the store's `chat-inline` slot mounts. Forwarded to the trail. */
|
|
43
|
+
this.inlineWidgets = [];
|
|
44
|
+
/** Registry-backed mounter for inline widgets whose id is not their
|
|
45
|
+
* custom-element tag (product card, comparison, FAQ). Set by the
|
|
46
|
+
* mountable from `runtime.widgets`; forwarded to the trail. */
|
|
47
|
+
this.mountInlineWidget = void 0;
|
|
48
|
+
/**
|
|
49
|
+
* Live reasoning narration from the model — streamed in via
|
|
50
|
+
* AG-UI THINKING_TEXT_MESSAGE_CONTENT events and forwarded by
|
|
51
|
+
* the ChatSession subscription on the host (mountable).
|
|
52
|
+
* Forwarded verbatim to the trail, which renders it in place of
|
|
53
|
+
* the silent loading dots between turns. Empty string when no
|
|
54
|
+
* thinking turn is active.
|
|
55
|
+
*/
|
|
56
|
+
this.thinkingText = "";
|
|
57
|
+
/**
|
|
58
|
+
* Pre-conversation suggestion button — forwarded to the trail's
|
|
59
|
+
* empty-state pane. When the visitor clicks it the trail emits
|
|
60
|
+
* `trail-intro-suggestion`; we catch that here and submit the
|
|
61
|
+
* configured prompt through the normal send path.
|
|
62
|
+
*/
|
|
63
|
+
this.introSuggestion = void 0;
|
|
64
|
+
/** Pass-through to {@link AdaptiveChatTrail}'s ``forceExpanded`` prop.
|
|
65
|
+
* Hosts that mount the chat-bar in a full-screen surface (mobile
|
|
66
|
+
* panel, agent app, etc.) set this to skip the collapse affordance
|
|
67
|
+
* — there is no smaller state to fall back to. */
|
|
68
|
+
this.forceExpanded = false;
|
|
69
|
+
this.placeholder = DEFAULT_PLACEHOLDER;
|
|
70
|
+
/**
|
|
71
|
+
* Name shown in the (forceExpanded) header. Defaults to a generic
|
|
72
|
+
* "Assistant". Hosts can override per-workspace (e.g. Vela sets
|
|
73
|
+
* "Vela Concierge") without inventing new SDK surface — it threads
|
|
74
|
+
* through the same mount config the placeholder does.
|
|
75
|
+
*/
|
|
76
|
+
this.assistantName = DEFAULT_ASSISTANT_NAME;
|
|
77
|
+
/** Persona roster from config (FEAT-1784917430); one is sticky-assigned. */
|
|
78
|
+
this.personas = null;
|
|
79
|
+
/** @internal — the visitor's sticky persona, resolved from the roster. */
|
|
80
|
+
this._persona = null;
|
|
81
|
+
/**
|
|
82
|
+
* Initial assistant message shown before any real conversation
|
|
83
|
+
* starts. Renders inside the trail as a phantom assistant chip
|
|
84
|
+
* when messages.length === 0; auto-disappears as soon as the
|
|
85
|
+
* first user message lands. Mirrors ChatAssistantLit's greeting.
|
|
86
|
+
*/
|
|
87
|
+
this.greeting = void 0;
|
|
88
|
+
/**
|
|
89
|
+
* Whether a chat round-trip is in flight (request sent, awaiting
|
|
90
|
+
* reply). The single send/stop button morphs accordingly: ↑ when
|
|
91
|
+
* ready, ⏹ when in-flight. Parent owns this state — set true after
|
|
92
|
+
* `chat-message-sent` fires, false when the reply lands or the
|
|
93
|
+
* request is aborted. Enter is inert while in-flight.
|
|
94
|
+
*/
|
|
95
|
+
this.inFlight = false;
|
|
96
|
+
/** Show a dictation mic when the Web Speech API is available. Config-gated, default off. */
|
|
97
|
+
this.voiceInput = false;
|
|
98
|
+
this._input = "";
|
|
99
|
+
this._micListening = false;
|
|
100
|
+
this._micDisabled = false;
|
|
101
|
+
this._micBase = "";
|
|
102
|
+
this._micNetworkErrors = 0;
|
|
103
|
+
this._dictation = null;
|
|
104
|
+
/** True while focus is inside the composer row — drives the accent
|
|
105
|
+
* focus ring so the active text-entry surface is unmistakable on any
|
|
106
|
+
* theme (the idle pill is deliberately subtle). */
|
|
107
|
+
this._composerFocused = false;
|
|
108
|
+
// Host-level styling (display, width, height, flex, etc.) is owned
|
|
109
|
+
// by the container — SDK shadow root provides default rules, panel
|
|
110
|
+
// overrides via design tokens (--sc-chat-bar-host-*). This adaptive
|
|
111
|
+
// never sets inline styles on itself. See SmartCanvasElementLit's
|
|
112
|
+
// static styles for the rule.
|
|
113
|
+
this._onInput = (e) => {
|
|
114
|
+
if (this._micListening) this._dictation?.stop();
|
|
115
|
+
this._input = e.target.value;
|
|
116
|
+
};
|
|
117
|
+
this._onMicClick = () => {
|
|
118
|
+
if (this._micListening) {
|
|
119
|
+
this._dictation?.stop();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this._micBase = this._input;
|
|
123
|
+
this._dictation = createDictation({
|
|
124
|
+
onTranscript: (text) => {
|
|
125
|
+
this._micNetworkErrors = 0;
|
|
126
|
+
this._input = this._micBase + (this._micBase ? " " : "") + text;
|
|
127
|
+
},
|
|
128
|
+
onEnd: () => {
|
|
129
|
+
this._micListening = false;
|
|
130
|
+
},
|
|
131
|
+
onError: (code) => {
|
|
132
|
+
this._micListening = false;
|
|
133
|
+
if (code === "not-allowed") this._micDisabled = true;
|
|
134
|
+
if (code === "network" && ++this._micNetworkErrors >= 2) this._micDisabled = true;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
this._dictation.start();
|
|
138
|
+
this._micListening = this._dictation.listening;
|
|
139
|
+
};
|
|
140
|
+
this._onKeyDown = (e) => {
|
|
141
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
if (this.inFlight) return;
|
|
144
|
+
this._send();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
// Single button. Click delegates to send-or-interrupt based on
|
|
148
|
+
// current state. Same DOM node, different behavior — discoverability
|
|
149
|
+
// wins from the user not having to scan for which control is active.
|
|
150
|
+
this._onSendOrInterrupt = () => {
|
|
151
|
+
if (this.inFlight) {
|
|
152
|
+
this._queuedSuggestedReply = null;
|
|
153
|
+
this.dispatchEvent(new CustomEvent("chat-interrupt", { bubbles: true, composed: true }));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this._send();
|
|
157
|
+
};
|
|
158
|
+
this._onTileReceiptActivate = (e) => {
|
|
159
|
+
const { instanceId } = e.detail;
|
|
160
|
+
getElementInstanceStore()?.recreate(instanceId);
|
|
161
|
+
this.dispatchEvent(
|
|
162
|
+
new CustomEvent("tile-recall", { detail: { instanceId }, bubbles: true, composed: true })
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
this._onAlertReceiptActivate = (e) => {
|
|
166
|
+
const { instanceId } = e.detail;
|
|
167
|
+
this.dispatchEvent(
|
|
168
|
+
new CustomEvent("alert-recall", { detail: { instanceId }, bubbles: true, composed: true })
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
this._onComposerFocusIn = () => {
|
|
172
|
+
this._composerFocused = true;
|
|
173
|
+
};
|
|
174
|
+
this._onComposerFocusOut = () => {
|
|
175
|
+
this._composerFocused = false;
|
|
176
|
+
};
|
|
177
|
+
this._onMaximizeToggle = () => {
|
|
178
|
+
this.dispatchEvent(new CustomEvent("chat-maximize-toggle", { bubbles: true, composed: true }));
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Visitor tapped the intro suggestion button in the empty-state
|
|
182
|
+
* trail. Submit the configured prompt through the same channel as
|
|
183
|
+
* a typed message so listeners (mountable → chatSession.send) see
|
|
184
|
+
* a uniform shape — no need for a parallel "suggestion-sent" path.
|
|
185
|
+
* The trail's intro pane auto-disappears the moment the first
|
|
186
|
+
* message lands, so this also closes the empty state in one go.
|
|
187
|
+
*/
|
|
188
|
+
this._onIntroSuggestion = (e) => {
|
|
189
|
+
const detail = e.detail;
|
|
190
|
+
if (!detail?.prompt) return;
|
|
191
|
+
e.stopPropagation();
|
|
192
|
+
if (this.inFlight) return;
|
|
193
|
+
this.inFlight = true;
|
|
194
|
+
this.dispatchEvent(
|
|
195
|
+
new CustomEvent("chat-message-sent", {
|
|
196
|
+
detail: { text: detail.prompt },
|
|
197
|
+
bubbles: true,
|
|
198
|
+
composed: true
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* Reply-chip tap that arrived during the run TAIL. Chips render the
|
|
204
|
+
* moment the reply text lands, but `inFlight` (which includes
|
|
205
|
+
* `runActive` — typing events, tool tail, RUN_FINISHED) can lag for
|
|
206
|
+
* seconds. The old `if (this.inFlight) return;` guard silently ATE
|
|
207
|
+
* those taps — the visitor's chosen reply never appeared as their
|
|
208
|
+
* message (live repro, deployed vela 2026-07-08). Now the tap queues
|
|
209
|
+
* (latest wins) and dispatches when `inFlight` clears.
|
|
210
|
+
*/
|
|
211
|
+
this._queuedSuggestedReply = null;
|
|
212
|
+
/**
|
|
213
|
+
* Visitor tapped an inline `suggest_replies` chip in the trail. Submit
|
|
214
|
+
* the chip text through the SAME `chat-message-sent` channel a typed
|
|
215
|
+
* message uses, so the reply rides the existing send path (mountable →
|
|
216
|
+
* chatSession.send → transport) — no fabricated AG-UI events. The
|
|
217
|
+
* trail clears the chips on its own once the reply lands and a newer
|
|
218
|
+
* assistant turn becomes the latest.
|
|
219
|
+
*
|
|
220
|
+
* A tap while the previous run is still closing is QUEUED, never
|
|
221
|
+
* dropped — see `_queuedSuggestedReply`.
|
|
222
|
+
*/
|
|
223
|
+
this._onSuggestedReply = (e) => {
|
|
224
|
+
const detail = e.detail;
|
|
225
|
+
if (!detail?.text) return;
|
|
226
|
+
e.stopPropagation();
|
|
227
|
+
if (this.inFlight) {
|
|
228
|
+
this._queuedSuggestedReply = detail.text;
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
this.inFlight = true;
|
|
232
|
+
this.dispatchEvent(
|
|
233
|
+
new CustomEvent("chat-message-sent", {
|
|
234
|
+
detail: { text: detail.text },
|
|
235
|
+
bubbles: true,
|
|
236
|
+
composed: true
|
|
237
|
+
})
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
createRenderRoot() {
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
connectedCallback() {
|
|
245
|
+
super.connectedCallback();
|
|
246
|
+
this.addEventListener("tile-receipt-activate", this._onTileReceiptActivate);
|
|
247
|
+
this.addEventListener("alert-receipt-activate", this._onAlertReceiptActivate);
|
|
248
|
+
}
|
|
249
|
+
disconnectedCallback() {
|
|
250
|
+
super.disconnectedCallback();
|
|
251
|
+
this.removeEventListener("tile-receipt-activate", this._onTileReceiptActivate);
|
|
252
|
+
this.removeEventListener("alert-receipt-activate", this._onAlertReceiptActivate);
|
|
253
|
+
}
|
|
254
|
+
_send() {
|
|
255
|
+
if (this._micListening) this._dictation?.stop();
|
|
256
|
+
const text = this._input.trim();
|
|
257
|
+
if (!text) return;
|
|
258
|
+
this._queuedSuggestedReply = null;
|
|
259
|
+
this.dispatchEvent(
|
|
260
|
+
new CustomEvent("chat-message-sent", {
|
|
261
|
+
detail: { text },
|
|
262
|
+
bubbles: true,
|
|
263
|
+
composed: true
|
|
264
|
+
})
|
|
265
|
+
);
|
|
266
|
+
this._input = "";
|
|
267
|
+
const input = this.querySelector("input[data-chat-input]");
|
|
268
|
+
if (input) input.value = "";
|
|
269
|
+
}
|
|
270
|
+
willUpdate(changed) {
|
|
271
|
+
super.willUpdate?.(changed);
|
|
272
|
+
if (changed.has("personas")) {
|
|
273
|
+
this._persona = pickPersona(this.personas);
|
|
274
|
+
}
|
|
275
|
+
if (changed.has("inFlight") && changed.get("inFlight") === true && !this.inFlight && this._queuedSuggestedReply) {
|
|
276
|
+
const text = this._queuedSuggestedReply;
|
|
277
|
+
this._queuedSuggestedReply = null;
|
|
278
|
+
this.inFlight = true;
|
|
279
|
+
this.dispatchEvent(
|
|
280
|
+
new CustomEvent("chat-message-sent", {
|
|
281
|
+
detail: { text },
|
|
282
|
+
bubbles: true,
|
|
283
|
+
composed: true
|
|
284
|
+
})
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
render() {
|
|
289
|
+
return html`
|
|
290
|
+
<div style=${styleMap(rootStyles(this.forceExpanded))}>
|
|
291
|
+
${this.forceExpanded ? renderHeader(this.assistantName, this._persona) : nothing}
|
|
292
|
+
<adaptive-chat-trail
|
|
293
|
+
.messages=${this.messages}
|
|
294
|
+
.greeting=${this._persona?.intro_line ?? this.greeting}
|
|
295
|
+
.forceExpanded=${this.forceExpanded}
|
|
296
|
+
.inFlight=${this.inFlight}
|
|
297
|
+
.thinkingText=${this.thinkingText}
|
|
298
|
+
.personaName=${this._persona?.name ?? ""}
|
|
299
|
+
.personaAvatarSm=${this._persona?.avatar?.sm ?? ""}
|
|
300
|
+
.introSuggestion=${this.introSuggestion}
|
|
301
|
+
.tileReceipts=${this.tileReceipts}
|
|
302
|
+
.inlineWidgets=${this.inlineWidgets}
|
|
303
|
+
.mountInlineWidget=${this.mountInlineWidget}
|
|
304
|
+
@trail-intro-suggestion=${this._onIntroSuggestion}
|
|
305
|
+
@trail-suggested-reply=${this._onSuggestedReply}
|
|
306
|
+
></adaptive-chat-trail>
|
|
307
|
+
<div
|
|
308
|
+
style=${styleMap(rowStyles(this.forceExpanded, this._composerFocused))}
|
|
309
|
+
data-chat-row
|
|
310
|
+
@focusin=${this._onComposerFocusIn}
|
|
311
|
+
@focusout=${this._onComposerFocusOut}
|
|
312
|
+
>
|
|
313
|
+
<button
|
|
314
|
+
type="button"
|
|
315
|
+
data-chat-maximize
|
|
316
|
+
@click=${this._onMaximizeToggle}
|
|
317
|
+
aria-label=${this.maximized ? "Exit full screen" : "Maximize chat"}
|
|
318
|
+
title=${this.maximized ? "Exit full screen" : "Maximize"}
|
|
319
|
+
aria-pressed=${this.maximized ? "true" : "false"}
|
|
320
|
+
style=${styleMap(maximizeStyles(this.maximized))}
|
|
321
|
+
>${this.maximized ? MINIMIZE_ICON : MAXIMIZE_ICON}</button>
|
|
322
|
+
<input
|
|
323
|
+
data-chat-input
|
|
324
|
+
type="text"
|
|
325
|
+
placeholder=${this._micListening ? "Listening \u2014 tap the mic to stop" : this.placeholder}
|
|
326
|
+
.value=${this._input}
|
|
327
|
+
@input=${this._onInput}
|
|
328
|
+
@keydown=${this._onKeyDown}
|
|
329
|
+
style=${styleMap(inputStyles())}
|
|
330
|
+
aria-label="Chat input"
|
|
331
|
+
/>
|
|
332
|
+
${this.voiceInput && isDictationSupported() ? html`${MIC_RING_STYLES}<button
|
|
333
|
+
type="button"
|
|
334
|
+
data-chat-mic
|
|
335
|
+
data-listening=${this._micListening ? "true" : "false"}
|
|
336
|
+
?disabled=${this._micDisabled}
|
|
337
|
+
@click=${this._onMicClick}
|
|
338
|
+
aria-pressed=${this._micListening ? "true" : "false"}
|
|
339
|
+
aria-label=${this._micListening ? "Stop dictation" : "Start dictation"}
|
|
340
|
+
title=${this._micDisabled ? "Microphone blocked" : this._micListening ? "Stop dictation" : "Dictate"}
|
|
341
|
+
style=${styleMap(micStyles(this._micListening, this._micDisabled))}
|
|
342
|
+
>${MIC_ICON}</button>` : nothing}
|
|
343
|
+
<button
|
|
344
|
+
type="button"
|
|
345
|
+
data-chat-send
|
|
346
|
+
data-state=${this.inFlight ? "in-flight" : "ready"}
|
|
347
|
+
@click=${this._onSendOrInterrupt}
|
|
348
|
+
aria-label=${this.inFlight ? "Stop" : "Send message"}
|
|
349
|
+
title=${this.inFlight ? "Interrupt" : "Send"}
|
|
350
|
+
style=${styleMap(sendStyles(this.inFlight))}
|
|
351
|
+
>${this.inFlight ? "\u25A0" : "\u2191"}</button>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
`;
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
AdaptiveChatBar.properties = {
|
|
358
|
+
personas: { attribute: false },
|
|
359
|
+
_persona: { state: true },
|
|
360
|
+
messages: { attribute: false },
|
|
361
|
+
placeholder: { type: String },
|
|
362
|
+
assistantName: { type: String },
|
|
363
|
+
greeting: { type: String },
|
|
364
|
+
inFlight: { type: Boolean, reflect: true },
|
|
365
|
+
thinkingText: { attribute: false },
|
|
366
|
+
introSuggestion: { attribute: false },
|
|
367
|
+
forceExpanded: { type: Boolean, reflect: true },
|
|
368
|
+
maximized: { type: Boolean },
|
|
369
|
+
tileReceipts: { attribute: false },
|
|
370
|
+
inlineWidgets: { attribute: false },
|
|
371
|
+
mountInlineWidget: { attribute: false },
|
|
372
|
+
voiceInput: { type: Boolean },
|
|
373
|
+
_input: { state: true },
|
|
374
|
+
_composerFocused: { state: true },
|
|
375
|
+
_micListening: { state: true },
|
|
376
|
+
_micDisabled: { state: true }
|
|
377
|
+
};
|
|
378
|
+
function rootStyles(forceExpanded) {
|
|
379
|
+
if (!forceExpanded) {
|
|
380
|
+
return {
|
|
381
|
+
display: "flex",
|
|
382
|
+
flexDirection: "column",
|
|
383
|
+
gap: "6px",
|
|
384
|
+
width: "100%",
|
|
385
|
+
pointerEvents: "auto"
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
position: "relative",
|
|
390
|
+
display: "flex",
|
|
391
|
+
flexDirection: "column",
|
|
392
|
+
gap: "8px",
|
|
393
|
+
width: "100%",
|
|
394
|
+
height: "100%",
|
|
395
|
+
boxSizing: "border-box",
|
|
396
|
+
flex: "1 1 auto",
|
|
397
|
+
minHeight: "0",
|
|
398
|
+
overflow: "hidden",
|
|
399
|
+
pointerEvents: "auto",
|
|
400
|
+
background: "var(--sc-tile-background, rgba(255, 255, 255, 0.92))",
|
|
401
|
+
border: "var(--sc-tile-border, 1px solid rgba(0, 0, 0, 0.06))",
|
|
402
|
+
borderRadius: "var(--sc-tile-border-radius, 14px)",
|
|
403
|
+
boxShadow: "var(--sc-tile-shadow, 0 12px 32px -16px rgba(0, 0, 0, 0.18))",
|
|
404
|
+
// Reserves room for the absolute-positioned input pill (36px high
|
|
405
|
+
// + 12px from bottom) plus a 16px visual gap above the input.
|
|
406
|
+
padding: "0.75rem 0.75rem calc(36px + 1.75rem)",
|
|
407
|
+
color: "var(--sc-tile-title-color, inherit)",
|
|
408
|
+
fontFamily: "var(--sc-font-family, inherit)",
|
|
409
|
+
// Override the trail's host-layout tokens so the trail (a flex item
|
|
410
|
+
// of this column) fills a definite-height parent AND contributes its
|
|
411
|
+
// content height in an indefinite one. basis AUTO (not 0) is
|
|
412
|
+
// load-bearing: grow:1 still fills a full-screen panel, but basis auto
|
|
413
|
+
// also lets the trail's content height propagate up the fill chain so a
|
|
414
|
+
// SHRINK-WRAP host (velvet's bottom sheet) hugs the content with no void.
|
|
415
|
+
// A basis-0 layer anywhere in the chain reports 0 min-content and
|
|
416
|
+
// collapses the sheet to header+input. shrink:1 + min-height:0 still lets
|
|
417
|
+
// the trail yield first when space is tight (input is absolute-pinned).
|
|
418
|
+
// The SDK-level rule in SmartCanvasElementLit consumes these tokens on
|
|
419
|
+
// adaptive-chat-trail's host element. Containers OWN host styling
|
|
420
|
+
// via tokens; the adaptive never sets inline styles on its host.
|
|
421
|
+
["--sc-chat-trail-host-display"]: "flex",
|
|
422
|
+
["--sc-chat-trail-host-flex-direction"]: "column",
|
|
423
|
+
["--sc-chat-trail-host-flex"]: "1 1 auto",
|
|
424
|
+
["--sc-chat-trail-host-min-height"]: "0",
|
|
425
|
+
["--sc-chat-trail-host-overflow"]: "hidden"
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
var COMPOSER_FOCUS_BORDER = "1px solid hsl(var(--sc-accent-color) / 0.6)";
|
|
429
|
+
function rowStyles(forceExpanded, focused) {
|
|
430
|
+
if (forceExpanded) {
|
|
431
|
+
return {
|
|
432
|
+
position: "absolute",
|
|
433
|
+
left: "0.75rem",
|
|
434
|
+
right: "0.75rem",
|
|
435
|
+
bottom: "0.75rem",
|
|
436
|
+
display: "flex",
|
|
437
|
+
alignItems: "center",
|
|
438
|
+
gap: "8px",
|
|
439
|
+
padding: "6px 14px",
|
|
440
|
+
borderRadius: "9999px",
|
|
441
|
+
background: "var(--sc-chat-composer-background, color-mix(in srgb, currentColor 8%, transparent))",
|
|
442
|
+
border: focused ? COMPOSER_FOCUS_BORDER : "1px solid var(--sc-chat-composer-border, color-mix(in srgb, currentColor 22%, transparent))",
|
|
443
|
+
color: "var(--sc-content-text-color, inherit)",
|
|
444
|
+
fontFamily: "var(--sc-font-family, inherit)",
|
|
445
|
+
minHeight: "36px"
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
display: "flex",
|
|
450
|
+
alignItems: "center",
|
|
451
|
+
gap: "8px",
|
|
452
|
+
padding: "10px 12px",
|
|
453
|
+
borderRadius: "var(--sc-tile-border-radius, 14px)",
|
|
454
|
+
background: "var(--sc-tile-background, rgba(15, 19, 24, 0.6))",
|
|
455
|
+
backdropFilter: "var(--sc-chat-bar-blur, blur(24px) saturate(140%))",
|
|
456
|
+
WebkitBackdropFilter: "var(--sc-chat-bar-blur, blur(24px) saturate(140%))",
|
|
457
|
+
border: focused ? COMPOSER_FOCUS_BORDER : "var(--sc-tile-border, 1px solid rgba(255, 255, 255, 0.08))",
|
|
458
|
+
boxShadow: "var(--sc-tile-shadow, 0 2px 12px rgba(0, 0, 0, 0.3))",
|
|
459
|
+
color: "var(--sc-tile-title-color, #fafafa)",
|
|
460
|
+
fontFamily: "var(--sc-font-family, inherit)"
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
var MIC_ICON = svg`<svg viewBox="0 0 16 16" width="16" height="16" fill="none" aria-hidden="true"><rect x="6" y="1.5" width="4" height="8" rx="2" stroke="currentColor" stroke-width="1.6"></rect><path d="M3.5 7.5a4.5 4.5 0 0 0 9 0M8 12v2.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg>`;
|
|
464
|
+
function micStyles(listening, disabled) {
|
|
465
|
+
return {
|
|
466
|
+
display: "inline-flex",
|
|
467
|
+
"align-items": "center",
|
|
468
|
+
"justify-content": "center",
|
|
469
|
+
width: "32px",
|
|
470
|
+
height: "32px",
|
|
471
|
+
border: "none",
|
|
472
|
+
"border-radius": "50%",
|
|
473
|
+
// Listening is a MODE: the mic flips to a filled primary chip with a
|
|
474
|
+
// ripple ring (MIC_RING_STYLES) so recording is unmissable, and the
|
|
475
|
+
// input placeholder says how to end it.
|
|
476
|
+
background: listening ? "var(--sc-color-primary, #6a59ce)" : "transparent",
|
|
477
|
+
color: listening ? "var(--sc-color-primary-text, #ffffff)" : "currentColor",
|
|
478
|
+
opacity: disabled ? "0.3" : listening ? "1" : "0.65",
|
|
479
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
480
|
+
transition: "color 150ms ease, background 150ms ease, opacity 150ms ease",
|
|
481
|
+
"flex-shrink": "0"
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
var MIC_RING_STYLES = html`<style>
|
|
485
|
+
[data-chat-mic][data-listening='true'] {
|
|
486
|
+
animation: sc-chatbar-mic-ring 1.5s ease-out infinite;
|
|
487
|
+
}
|
|
488
|
+
@keyframes sc-chatbar-mic-ring {
|
|
489
|
+
0% {
|
|
490
|
+
box-shadow: 0 0 0 0 color-mix(in srgb, var(--sc-color-primary, #6a59ce) 45%, transparent);
|
|
491
|
+
transform: scale(1);
|
|
492
|
+
}
|
|
493
|
+
50% { transform: scale(1.08); }
|
|
494
|
+
70% { box-shadow: 0 0 0 12px transparent; }
|
|
495
|
+
100% {
|
|
496
|
+
box-shadow: 0 0 0 0 transparent;
|
|
497
|
+
transform: scale(1);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
@media (prefers-reduced-motion: reduce) {
|
|
501
|
+
[data-chat-mic][data-listening='true'] {
|
|
502
|
+
animation: none;
|
|
503
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--sc-color-primary, #6a59ce) 35%, transparent);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
</style>`;
|
|
507
|
+
var MAXIMIZE_ICON = svg`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" y1="3" x2="14" y2="10"></line><line x1="3" y1="21" x2="10" y2="14"></line></svg>`;
|
|
508
|
+
var MINIMIZE_ICON = svg`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="4 14 10 14 10 20"></polyline><polyline points="20 10 14 10 14 4"></polyline><line x1="14" y1="10" x2="21" y2="3"></line><line x1="3" y1="21" x2="10" y2="14"></line></svg>`;
|
|
509
|
+
function maximizeStyles(maximized) {
|
|
510
|
+
return {
|
|
511
|
+
display: "inline-flex",
|
|
512
|
+
alignItems: "center",
|
|
513
|
+
justifyContent: "center",
|
|
514
|
+
width: "32px",
|
|
515
|
+
height: "32px",
|
|
516
|
+
borderRadius: "9999px",
|
|
517
|
+
// Active (maximized): filled accent chip. Idle: subtle accent chip with a
|
|
518
|
+
// visible ring. Colour is full-opacity in both states (no 0.95 ghosting).
|
|
519
|
+
background: maximized ? "hsl(var(--sc-accent-color) / 0.85)" : "hsl(var(--sc-accent-color) / 0.15)",
|
|
520
|
+
border: maximized ? "none" : "1px solid hsl(var(--sc-accent-color) / 0.45)",
|
|
521
|
+
color: maximized ? "var(--sc-accent-foreground, #fff)" : "hsl(var(--sc-accent-color))",
|
|
522
|
+
flexShrink: "0",
|
|
523
|
+
lineHeight: "1",
|
|
524
|
+
padding: "0",
|
|
525
|
+
cursor: "pointer",
|
|
526
|
+
transition: "background 150ms ease, color 150ms ease, border-color 150ms ease"
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function inputStyles() {
|
|
530
|
+
return {
|
|
531
|
+
flex: "1",
|
|
532
|
+
minWidth: "0",
|
|
533
|
+
background: "transparent",
|
|
534
|
+
border: "none",
|
|
535
|
+
outline: "none",
|
|
536
|
+
color: "inherit",
|
|
537
|
+
// MUST be >= 16px. iOS Safari auto-zooms the page when a focused input has a
|
|
538
|
+
// font-size below 16px, then does NOT zoom back out on blur — so the composer
|
|
539
|
+
// zoomed in, the input ended up behind the keyboard (the zoom breaks the
|
|
540
|
+
// visualViewport keyboard-lift math), and tapping "Done" left the whole site
|
|
541
|
+
// stuck zoomed in. 16px is the standard, accessibility-safe fix (no
|
|
542
|
+
// user-scalable=no / maximum-scale hacks that would kill pinch-zoom).
|
|
543
|
+
fontSize: "16px"
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function sendStyles(inFlight) {
|
|
547
|
+
return {
|
|
548
|
+
width: "28px",
|
|
549
|
+
height: "28px",
|
|
550
|
+
display: "inline-flex",
|
|
551
|
+
alignItems: "center",
|
|
552
|
+
justifyContent: "center",
|
|
553
|
+
borderRadius: "9999px",
|
|
554
|
+
// Ready: filled accent (send is the affordance). In-flight: hollow
|
|
555
|
+
// ring + accent ring (stop is a one-of-a-kind disruptive action,
|
|
556
|
+
// styled differently so the user reads "this isn't just send").
|
|
557
|
+
background: inFlight ? "transparent" : "hsl(var(--sc-accent-color) / 0.85)",
|
|
558
|
+
color: inFlight ? "hsl(var(--sc-accent-color) / 0.95)" : "var(--sc-accent-foreground, #fff)",
|
|
559
|
+
border: inFlight ? "1px solid hsl(var(--sc-accent-color) / 0.55)" : "none",
|
|
560
|
+
cursor: "pointer",
|
|
561
|
+
flexShrink: "0",
|
|
562
|
+
transition: "background 150ms ease, color 150ms ease, border-color 150ms ease"
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function renderHeader(assistantName, persona) {
|
|
566
|
+
const headerStyles = {
|
|
567
|
+
display: "flex",
|
|
568
|
+
alignItems: "center",
|
|
569
|
+
gap: "7px",
|
|
570
|
+
// Halved vertical footprint: 6px total block padding vs the old 16px
|
|
571
|
+
// (4px top + 12px bottom). Horizontal padding unchanged.
|
|
572
|
+
padding: "3px 4px",
|
|
573
|
+
borderBottom: "1px solid var(--sc-content-divider-color, rgba(0, 0, 0, 0.08))",
|
|
574
|
+
flex: "0 0 auto"
|
|
575
|
+
};
|
|
576
|
+
const avatar = {
|
|
577
|
+
display: "inline-flex",
|
|
578
|
+
alignItems: "center",
|
|
579
|
+
justifyContent: "center",
|
|
580
|
+
width: "16px",
|
|
581
|
+
height: "16px",
|
|
582
|
+
borderRadius: "9999px",
|
|
583
|
+
background: "var(--sc-tile-icon-background, linear-gradient(135deg, var(--sc-color-primary, #5faf7d) 0%, var(--sc-color-primary-hover, #3d8a5e) 100%))",
|
|
584
|
+
color: "#fff",
|
|
585
|
+
flexShrink: "0",
|
|
586
|
+
fontSize: "9px",
|
|
587
|
+
lineHeight: "1"
|
|
588
|
+
};
|
|
589
|
+
const name = {
|
|
590
|
+
fontSize: "0.8rem",
|
|
591
|
+
fontWeight: "600",
|
|
592
|
+
color: "var(--sc-content-text-color, inherit)",
|
|
593
|
+
lineHeight: "1.2",
|
|
594
|
+
flexShrink: "0"
|
|
595
|
+
};
|
|
596
|
+
const personaImg = {
|
|
597
|
+
width: "22px",
|
|
598
|
+
height: "22px",
|
|
599
|
+
borderRadius: "9999px",
|
|
600
|
+
objectFit: "cover",
|
|
601
|
+
flexShrink: "0"
|
|
602
|
+
};
|
|
603
|
+
const disclosure = {
|
|
604
|
+
fontSize: "0.62rem",
|
|
605
|
+
fontWeight: "500",
|
|
606
|
+
color: "var(--sc-content-text-color, inherit)",
|
|
607
|
+
opacity: "0.55",
|
|
608
|
+
lineHeight: "1.2",
|
|
609
|
+
flexShrink: "0"
|
|
610
|
+
};
|
|
611
|
+
return html`
|
|
612
|
+
<div style=${styleMap(headerStyles)} data-chat-header>
|
|
613
|
+
${persona?.avatar?.md || persona?.avatar?.sm ? html`<img
|
|
614
|
+
style=${styleMap(personaImg)}
|
|
615
|
+
src=${persona.avatar.md ?? persona.avatar.sm ?? ""}
|
|
616
|
+
alt=""
|
|
617
|
+
data-persona-avatar
|
|
618
|
+
/>` : html`<span style=${styleMap(avatar)} aria-hidden="true">✦</span>`}
|
|
619
|
+
<span style=${styleMap(name)} data-persona-name>${persona?.name ?? assistantName}</span>
|
|
620
|
+
${persona ? html`<span style=${styleMap(disclosure)} data-persona-disclosure>· AI concierge</span>` : nothing}
|
|
621
|
+
</div>
|
|
622
|
+
`;
|
|
623
|
+
}
|
|
624
|
+
if (!customElements.get("adaptive-chat-bar")) {
|
|
625
|
+
customElements.define("adaptive-chat-bar", AdaptiveChatBar);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export {
|
|
629
|
+
pickPersona,
|
|
630
|
+
AdaptiveChatBar
|
|
631
|
+
};
|
|
632
|
+
//# sourceMappingURL=chunk-FXHYE6FY.js.map
|