@noodleseed/assistant 1.1.1 → 1.2.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 +90 -7
- package/dist/chunk-J2YDKISA.js +1113 -0
- package/dist/chunk-J2YDKISA.js.map +1 -0
- package/dist/chunk-WUDHE5BD.js +636 -0
- package/dist/chunk-WUDHE5BD.js.map +1 -0
- package/dist/client-CwvdK4O6.d.cts +165 -0
- package/dist/client-DkAfqcSb.d.ts +165 -0
- package/dist/client.cjs +662 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +2 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +9 -0
- package/dist/client.js.map +1 -0
- package/dist/index.cjs +1273 -242
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -12
- package/dist/index.d.ts +14 -12
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1273 -242
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -1
- package/dist/react.d.ts +2 -1
- package/dist/react.js +2 -1
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +2 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +6 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/package.json +10 -1
- package/dist/chunk-IM7SAHNE.js +0 -706
- package/dist/chunk-IM7SAHNE.js.map +0 -1
|
@@ -0,0 +1,1113 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AssistantClientError,
|
|
3
|
+
copyAssistantModelContext,
|
|
4
|
+
createAssistantClient
|
|
5
|
+
} from "./chunk-WUDHE5BD.js";
|
|
6
|
+
|
|
7
|
+
// src/appearance.ts
|
|
8
|
+
var common = {
|
|
9
|
+
fontFamily: "inherit",
|
|
10
|
+
monoFontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
11
|
+
baseFontSize: 15,
|
|
12
|
+
messageFontSize: 15,
|
|
13
|
+
lineHeight: 1.5,
|
|
14
|
+
panelRadius: 20,
|
|
15
|
+
cardRadius: 14,
|
|
16
|
+
inputRadius: 14,
|
|
17
|
+
buttonRadius: 12,
|
|
18
|
+
launcherRadius: 999,
|
|
19
|
+
spacing: 12,
|
|
20
|
+
backdropBlur: 18,
|
|
21
|
+
motionScale: 1
|
|
22
|
+
};
|
|
23
|
+
var DEFAULT_APPEARANCE = {
|
|
24
|
+
brand: { name: "Assistant", logo: {}, avatar: {}, mark: {} },
|
|
25
|
+
theme: {
|
|
26
|
+
defaultMode: "auto",
|
|
27
|
+
light: {
|
|
28
|
+
...common,
|
|
29
|
+
page: "#F7F7F8",
|
|
30
|
+
panel: "#FFFFFF",
|
|
31
|
+
elevated: "#FFFFFF",
|
|
32
|
+
input: "#F1F1F3",
|
|
33
|
+
hover: "#ECECEF",
|
|
34
|
+
selected: "#E4E2FF",
|
|
35
|
+
text: "#18181B",
|
|
36
|
+
mutedText: "#71717A",
|
|
37
|
+
accent: "#5B4CF0",
|
|
38
|
+
accentText: "#FFFFFF",
|
|
39
|
+
link: "#4A3BD5",
|
|
40
|
+
divider: "#E4E4E7",
|
|
41
|
+
focus: "#5B4CF0",
|
|
42
|
+
success: "#16835D",
|
|
43
|
+
warning: "#A45B00",
|
|
44
|
+
danger: "#C92A45",
|
|
45
|
+
overlay: "#18181B66",
|
|
46
|
+
code: "#F1F1F3",
|
|
47
|
+
widgetFrame: "#E4E4E7",
|
|
48
|
+
shadow: "0 24px 80px rgba(24, 24, 27, 0.18)"
|
|
49
|
+
},
|
|
50
|
+
dark: {
|
|
51
|
+
...common,
|
|
52
|
+
page: "#111113",
|
|
53
|
+
panel: "#19191D",
|
|
54
|
+
elevated: "#222228",
|
|
55
|
+
input: "#26262C",
|
|
56
|
+
hover: "#303038",
|
|
57
|
+
selected: "#37315F",
|
|
58
|
+
text: "#FAFAFA",
|
|
59
|
+
mutedText: "#A1A1AA",
|
|
60
|
+
accent: "#A99FFF",
|
|
61
|
+
accentText: "#17131F",
|
|
62
|
+
link: "#B9B1FF",
|
|
63
|
+
divider: "#34343B",
|
|
64
|
+
focus: "#B9B1FF",
|
|
65
|
+
success: "#58C69A",
|
|
66
|
+
warning: "#F2B45F",
|
|
67
|
+
danger: "#FF8094",
|
|
68
|
+
overlay: "#00000099",
|
|
69
|
+
code: "#26262C",
|
|
70
|
+
widgetFrame: "#34343B",
|
|
71
|
+
shadow: "0 24px 90px rgba(0, 0, 0, 0.55)"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
layout: {
|
|
75
|
+
mode: "floating",
|
|
76
|
+
position: "bottom-right",
|
|
77
|
+
panelWidth: 400,
|
|
78
|
+
panelMinHeight: 420,
|
|
79
|
+
panelMaxHeight: 720,
|
|
80
|
+
zIndex: 2147483e3,
|
|
81
|
+
density: "comfortable",
|
|
82
|
+
mobileFullscreen: true
|
|
83
|
+
},
|
|
84
|
+
behavior: {
|
|
85
|
+
startOpen: false,
|
|
86
|
+
closeOnEscape: true,
|
|
87
|
+
closeOnOutsideClick: false,
|
|
88
|
+
showLauncher: true,
|
|
89
|
+
showHeader: true,
|
|
90
|
+
showAvatars: true,
|
|
91
|
+
showTimestamps: false
|
|
92
|
+
},
|
|
93
|
+
labels: {
|
|
94
|
+
welcomeHeading: "How can I help?",
|
|
95
|
+
welcomeMessage: "",
|
|
96
|
+
composerPlaceholder: "Ask a question\u2026",
|
|
97
|
+
send: "Send",
|
|
98
|
+
stop: "Stop",
|
|
99
|
+
close: "Close assistant",
|
|
100
|
+
open: "Open assistant",
|
|
101
|
+
confirm: "Confirm",
|
|
102
|
+
cancel: "Cancel",
|
|
103
|
+
retry: "Try again",
|
|
104
|
+
unavailable: "The assistant is temporarily unavailable.",
|
|
105
|
+
sessionExpired: "Your assistant session expired. Reopen it to continue."
|
|
106
|
+
},
|
|
107
|
+
suggestedPrompts: [],
|
|
108
|
+
locale: "en",
|
|
109
|
+
direction: "auto"
|
|
110
|
+
};
|
|
111
|
+
function resolveAppearance(input = {}) {
|
|
112
|
+
const branding = input.branding;
|
|
113
|
+
const assistant = input.assistant;
|
|
114
|
+
return {
|
|
115
|
+
brand: {
|
|
116
|
+
...DEFAULT_APPEARANCE.brand,
|
|
117
|
+
...branding?.name ? { name: branding.name } : {},
|
|
118
|
+
logo: themedAsset(branding?.logo),
|
|
119
|
+
avatar: themedAsset(branding?.avatar),
|
|
120
|
+
mark: themedAsset(branding?.mark)
|
|
121
|
+
},
|
|
122
|
+
theme: {
|
|
123
|
+
defaultMode: branding?.colorScheme ?? DEFAULT_APPEARANCE.theme.defaultMode,
|
|
124
|
+
light: resolveTheme("light", branding),
|
|
125
|
+
dark: resolveTheme("dark", branding)
|
|
126
|
+
},
|
|
127
|
+
layout: {
|
|
128
|
+
...DEFAULT_APPEARANCE.layout,
|
|
129
|
+
...branding?.density ? { density: branding.density } : {},
|
|
130
|
+
...assistant?.layout
|
|
131
|
+
},
|
|
132
|
+
behavior: { ...DEFAULT_APPEARANCE.behavior, ...assistant?.behavior },
|
|
133
|
+
labels: { ...DEFAULT_APPEARANCE.labels, ...assistant?.labels },
|
|
134
|
+
suggestedPrompts: assistant?.suggestedPrompts ?? DEFAULT_APPEARANCE.suggestedPrompts,
|
|
135
|
+
...assistant?.privacyUrl ? { privacyUrl: assistant.privacyUrl } : {},
|
|
136
|
+
...assistant?.termsUrl ? { termsUrl: assistant.termsUrl } : {},
|
|
137
|
+
locale: assistant?.locale ?? DEFAULT_APPEARANCE.locale,
|
|
138
|
+
direction: assistant?.direction ?? DEFAULT_APPEARANCE.direction
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function themedAsset(asset) {
|
|
142
|
+
return asset ? { light: asset.uri, dark: asset.darkUri ?? asset.uri } : {};
|
|
143
|
+
}
|
|
144
|
+
function resolveTheme(mode, branding) {
|
|
145
|
+
const defaults = DEFAULT_APPEARANCE.theme[mode];
|
|
146
|
+
const surface = mode === "light" ? branding?.surface : branding?.surfaceDark;
|
|
147
|
+
const portable = branding?.theme?.[mode];
|
|
148
|
+
const radius = branding?.radius ? radiusValue(branding.radius) : void 0;
|
|
149
|
+
return {
|
|
150
|
+
...defaults,
|
|
151
|
+
...branding?.accent ? { accent: branding.accent } : {},
|
|
152
|
+
...surface ? { page: surface, panel: surface } : {},
|
|
153
|
+
...mapPortableTheme(portable),
|
|
154
|
+
...radius === void 0 ? {} : {
|
|
155
|
+
panelRadius: radius,
|
|
156
|
+
cardRadius: radius,
|
|
157
|
+
inputRadius: Math.max(0, radius - 2),
|
|
158
|
+
buttonRadius: Math.max(0, radius - 2)
|
|
159
|
+
},
|
|
160
|
+
...branding?.typography === "serif" ? { fontFamily: "ui-serif, Georgia, serif" } : branding?.typography === "mono" ? { fontFamily: defaults.monoFontFamily } : {}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function mapPortableTheme(input) {
|
|
164
|
+
if (!input) return {};
|
|
165
|
+
return {
|
|
166
|
+
...input.surface ? { page: input.surface, panel: input.surface } : {},
|
|
167
|
+
...input.surfaceRaised ? { elevated: input.surfaceRaised } : {},
|
|
168
|
+
...input.surfaceMuted ? { input: input.surfaceMuted, hover: input.surfaceMuted, selected: input.surfaceMuted } : {},
|
|
169
|
+
...input.text ? { text: input.text } : {},
|
|
170
|
+
...input.textMuted ? { mutedText: input.textMuted } : {},
|
|
171
|
+
...input.accent ? { accent: input.accent } : {},
|
|
172
|
+
...input.accentText ? { accentText: input.accentText } : {},
|
|
173
|
+
...input.link ? { link: input.link } : {},
|
|
174
|
+
...input.border ? { divider: input.border, widgetFrame: input.border } : {},
|
|
175
|
+
...input.focus ? { focus: input.focus } : {},
|
|
176
|
+
...input.success ? { success: input.success } : {},
|
|
177
|
+
...input.warning ? { warning: input.warning } : {},
|
|
178
|
+
...input.danger ? { danger: input.danger } : {},
|
|
179
|
+
...input.code ? { code: input.code } : {}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function radiusValue(radius) {
|
|
183
|
+
if (radius === "none") return 0;
|
|
184
|
+
if (radius === "sm") return 8;
|
|
185
|
+
if (radius === "lg") return 18;
|
|
186
|
+
return 14;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/browser-context.ts
|
|
190
|
+
function browserClientContext() {
|
|
191
|
+
let timeZone;
|
|
192
|
+
try {
|
|
193
|
+
timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
const locale = globalThis.navigator?.language;
|
|
197
|
+
return {
|
|
198
|
+
...locale ? { locale } : {},
|
|
199
|
+
...timeZone ? { timeZone } : {}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/input-request-card.ts
|
|
204
|
+
function createInputRequestCard(options) {
|
|
205
|
+
const card = document.createElement("section");
|
|
206
|
+
card.className = "tool-proposal input-request";
|
|
207
|
+
card.dataset.kind = "input";
|
|
208
|
+
const form = document.createElement("form");
|
|
209
|
+
const heading = document.createElement("strong");
|
|
210
|
+
heading.textContent = options.message;
|
|
211
|
+
form.append(heading);
|
|
212
|
+
const properties = record(options.requestedSchema.properties);
|
|
213
|
+
const required = new Set(
|
|
214
|
+
Array.isArray(options.requestedSchema.required) ? options.requestedSchema.required.filter(
|
|
215
|
+
(value) => typeof value === "string"
|
|
216
|
+
) : []
|
|
217
|
+
);
|
|
218
|
+
const controls = /* @__PURE__ */ new Map();
|
|
219
|
+
const touched = /* @__PURE__ */ new WeakSet();
|
|
220
|
+
for (const [name, rawProperty] of Object.entries(properties)) {
|
|
221
|
+
const property = record(rawProperty);
|
|
222
|
+
const label = document.createElement("label");
|
|
223
|
+
const caption = document.createElement("span");
|
|
224
|
+
caption.textContent = typeof property.title === "string" && property.title ? property.title : humanize(name);
|
|
225
|
+
const control = createControl(name, property, required.has(name));
|
|
226
|
+
const markTouched = () => {
|
|
227
|
+
touched.add(control);
|
|
228
|
+
updateControlValidity(control, property, required.has(name), touched.has(control));
|
|
229
|
+
};
|
|
230
|
+
control.addEventListener("input", markTouched);
|
|
231
|
+
control.addEventListener("change", markTouched);
|
|
232
|
+
label.append(caption, control);
|
|
233
|
+
form.append(label);
|
|
234
|
+
controls.set(name, control);
|
|
235
|
+
}
|
|
236
|
+
const actions = document.createElement("div");
|
|
237
|
+
actions.className = "proposal-actions";
|
|
238
|
+
const submit = button(options.labels.submit, "submit");
|
|
239
|
+
const decline = button(options.labels.decline, "button");
|
|
240
|
+
const cancel = button(options.labels.cancel, "button");
|
|
241
|
+
const buttons = [submit, decline, cancel];
|
|
242
|
+
const resolve = async (response) => {
|
|
243
|
+
buttons.forEach((item) => {
|
|
244
|
+
item.disabled = true;
|
|
245
|
+
});
|
|
246
|
+
try {
|
|
247
|
+
await options.respond(response);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
buttons.forEach((item) => {
|
|
250
|
+
item.disabled = false;
|
|
251
|
+
});
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
form.addEventListener("submit", (event) => {
|
|
256
|
+
event.preventDefault();
|
|
257
|
+
for (const [name, control] of controls) {
|
|
258
|
+
updateControlValidity(
|
|
259
|
+
control,
|
|
260
|
+
record(properties[name]),
|
|
261
|
+
required.has(name),
|
|
262
|
+
touched.has(control)
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (!form.reportValidity()) return;
|
|
266
|
+
void resolve({
|
|
267
|
+
action: "accept",
|
|
268
|
+
content: readContent(controls, properties, required, touched)
|
|
269
|
+
}).catch(() => {
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
decline.addEventListener("click", () => {
|
|
273
|
+
void resolve({ action: "decline" }).catch(() => {
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
cancel.addEventListener("click", () => {
|
|
277
|
+
void resolve({ action: "cancel" }).catch(() => {
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
actions.append(submit, decline, cancel);
|
|
281
|
+
form.append(actions);
|
|
282
|
+
card.append(form);
|
|
283
|
+
return card;
|
|
284
|
+
}
|
|
285
|
+
function createControl(name, property, required) {
|
|
286
|
+
const choices = stringChoices(property);
|
|
287
|
+
if (choices || property.type === "array") {
|
|
288
|
+
const select = document.createElement("select");
|
|
289
|
+
select.name = name;
|
|
290
|
+
select.multiple = property.type === "array";
|
|
291
|
+
const availableChoices = choices ?? arrayChoices(property);
|
|
292
|
+
if (!select.multiple && (!required || !availableChoices.some((choice) => choice.value === property.default))) {
|
|
293
|
+
select.append(placeholderOption(availableChoices));
|
|
294
|
+
}
|
|
295
|
+
for (const choice of availableChoices) {
|
|
296
|
+
select.append(option(choice.title, choice.value));
|
|
297
|
+
}
|
|
298
|
+
applySelectDefault(select, property.default);
|
|
299
|
+
updateControlValidity(select, property, required, false);
|
|
300
|
+
return select;
|
|
301
|
+
}
|
|
302
|
+
const input = document.createElement("input");
|
|
303
|
+
input.name = name;
|
|
304
|
+
input.type = inputType(property);
|
|
305
|
+
input.required = required && inputNeedsNonEmptyValue(property);
|
|
306
|
+
if (property.type === "integer") input.step = "1";
|
|
307
|
+
if (typeof property.minimum === "number") input.min = String(property.minimum);
|
|
308
|
+
if (typeof property.maximum === "number") input.max = String(property.maximum);
|
|
309
|
+
if (typeof property.minLength === "number") input.minLength = property.minLength;
|
|
310
|
+
if (typeof property.maxLength === "number") input.maxLength = property.maxLength;
|
|
311
|
+
if (property.format === "date-time") input.step = "0.001";
|
|
312
|
+
applyInputDefault(input, property);
|
|
313
|
+
return input;
|
|
314
|
+
}
|
|
315
|
+
function readContent(controls, properties, required, touched) {
|
|
316
|
+
const content = {};
|
|
317
|
+
for (const [name, control] of controls) {
|
|
318
|
+
const property = record(properties[name]);
|
|
319
|
+
if (control instanceof HTMLSelectElement && control.multiple) {
|
|
320
|
+
const values = [...control.selectedOptions].map((option2) => option2.value);
|
|
321
|
+
if (shouldIncludeArray(property, required.has(name), touched.has(control), values.length)) {
|
|
322
|
+
content[name] = values;
|
|
323
|
+
}
|
|
324
|
+
} else if (control instanceof HTMLInputElement && control.type === "checkbox") {
|
|
325
|
+
if (required.has(name) || hasDefault(property) || touched.has(control)) {
|
|
326
|
+
content[name] = control.checked;
|
|
327
|
+
}
|
|
328
|
+
} else if (control instanceof HTMLSelectElement) {
|
|
329
|
+
if (!isPlaceholderSelected(control)) content[name] = control.value;
|
|
330
|
+
} else if (control.value !== "" || property.type === "string" && (required.has(name) || hasDefault(property) || touched.has(control))) {
|
|
331
|
+
if (property.format === "date-time") {
|
|
332
|
+
const value = dateTimeLocalToRfc3339(control.value);
|
|
333
|
+
if (value !== void 0) content[name] = value;
|
|
334
|
+
} else {
|
|
335
|
+
content[name] = property.type === "number" || property.type === "integer" ? Number(control.value) : control.value;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return content;
|
|
340
|
+
}
|
|
341
|
+
function updateControlValidity(control, property, required, touched) {
|
|
342
|
+
control.setCustomValidity("");
|
|
343
|
+
if (!(control instanceof HTMLSelectElement)) return;
|
|
344
|
+
if (!control.multiple) {
|
|
345
|
+
control.required = required;
|
|
346
|
+
if (required && isPlaceholderSelected(control)) {
|
|
347
|
+
control.setCustomValidity("Please select an option.");
|
|
348
|
+
}
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const count = control.selectedOptions.length;
|
|
352
|
+
const included = shouldIncludeArray(property, required, touched, count);
|
|
353
|
+
const minimum = integerBound(property.minItems);
|
|
354
|
+
const maximum = integerBound(property.maxItems);
|
|
355
|
+
control.required = included && minimum !== void 0 && minimum > 0;
|
|
356
|
+
if (included && minimum !== void 0 && count < minimum) {
|
|
357
|
+
control.setCustomValidity(`Select at least ${minimum} option${minimum === 1 ? "" : "s"}.`);
|
|
358
|
+
} else if (included && maximum !== void 0 && count > maximum) {
|
|
359
|
+
control.setCustomValidity(`Select at most ${maximum} option${maximum === 1 ? "" : "s"}.`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function shouldIncludeArray(property, required, touched, selectedCount) {
|
|
363
|
+
return required || hasDefault(property) || touched || selectedCount > 0;
|
|
364
|
+
}
|
|
365
|
+
function hasDefault(property) {
|
|
366
|
+
return Object.hasOwn(property, "default");
|
|
367
|
+
}
|
|
368
|
+
function integerBound(value) {
|
|
369
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
370
|
+
}
|
|
371
|
+
function inputNeedsNonEmptyValue(property) {
|
|
372
|
+
if (property.type === "number" || property.type === "integer") return true;
|
|
373
|
+
if (property.type !== "string") return false;
|
|
374
|
+
return property.format === "date" || property.format === "date-time" || property.format === "email" || property.format === "uri" || typeof property.minLength === "number" && property.minLength > 0;
|
|
375
|
+
}
|
|
376
|
+
function isPlaceholderSelected(select) {
|
|
377
|
+
return select.selectedOptions[0]?.dataset.placeholder === "true";
|
|
378
|
+
}
|
|
379
|
+
function stringChoices(property) {
|
|
380
|
+
if (Array.isArray(property.enum) && property.enum.every((value) => typeof value === "string")) {
|
|
381
|
+
return property.enum.map((value) => ({ value, title: value }));
|
|
382
|
+
}
|
|
383
|
+
return titledChoices(property.oneOf) ?? titledChoices(property.anyOf);
|
|
384
|
+
}
|
|
385
|
+
function arrayChoices(property) {
|
|
386
|
+
return stringChoices(record(property.items)) ?? [];
|
|
387
|
+
}
|
|
388
|
+
function inputType(property) {
|
|
389
|
+
if (property.type === "boolean") return "checkbox";
|
|
390
|
+
if (property.type === "number" || property.type === "integer") return "number";
|
|
391
|
+
if (property.format === "date") return "date";
|
|
392
|
+
if (property.format === "date-time") return "datetime-local";
|
|
393
|
+
if (property.format === "email") return "email";
|
|
394
|
+
if (property.format === "uri") return "url";
|
|
395
|
+
return "text";
|
|
396
|
+
}
|
|
397
|
+
function titledChoices(value) {
|
|
398
|
+
if (!Array.isArray(value)) return void 0;
|
|
399
|
+
const choices = value.map((item) => record(item));
|
|
400
|
+
if (!choices.every((item) => typeof item.const === "string")) return void 0;
|
|
401
|
+
return choices.map((item) => ({
|
|
402
|
+
value: String(item.const),
|
|
403
|
+
title: typeof item.title === "string" ? item.title : String(item.const)
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
function applySelectDefault(select, value) {
|
|
407
|
+
if (select.multiple) {
|
|
408
|
+
const selected = new Set(
|
|
409
|
+
Array.isArray(value) ? value.filter((item) => typeof item === "string") : []
|
|
410
|
+
);
|
|
411
|
+
for (const item of select.options) item.selected = selected.has(item.value);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (typeof value === "string") select.value = value;
|
|
415
|
+
}
|
|
416
|
+
function applyInputDefault(input, property) {
|
|
417
|
+
const value = property.default;
|
|
418
|
+
if (property.type === "boolean") {
|
|
419
|
+
if (typeof value === "boolean") input.checked = value;
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (property.type === "number" || property.type === "integer") {
|
|
423
|
+
if (typeof value === "number" && Number.isFinite(value)) input.value = String(value);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (typeof value !== "string") return;
|
|
427
|
+
if (property.format === "date-time") {
|
|
428
|
+
input.value = rfc3339ToDateTimeLocal(value) ?? "";
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
input.value = value;
|
|
432
|
+
}
|
|
433
|
+
function rfc3339ToDateTimeLocal(value) {
|
|
434
|
+
const instant = new Date(value);
|
|
435
|
+
if (Number.isNaN(instant.valueOf())) return void 0;
|
|
436
|
+
return [
|
|
437
|
+
String(instant.getFullYear()).padStart(4, "0"),
|
|
438
|
+
"-",
|
|
439
|
+
twoDigits(instant.getMonth() + 1),
|
|
440
|
+
"-",
|
|
441
|
+
twoDigits(instant.getDate()),
|
|
442
|
+
"T",
|
|
443
|
+
twoDigits(instant.getHours()),
|
|
444
|
+
":",
|
|
445
|
+
twoDigits(instant.getMinutes()),
|
|
446
|
+
":",
|
|
447
|
+
twoDigits(instant.getSeconds()),
|
|
448
|
+
".",
|
|
449
|
+
String(instant.getMilliseconds()).padStart(3, "0")
|
|
450
|
+
].join("");
|
|
451
|
+
}
|
|
452
|
+
function dateTimeLocalToRfc3339(value) {
|
|
453
|
+
const instant = new Date(value);
|
|
454
|
+
return Number.isNaN(instant.valueOf()) ? void 0 : instant.toISOString();
|
|
455
|
+
}
|
|
456
|
+
function twoDigits(value) {
|
|
457
|
+
return String(value).padStart(2, "0");
|
|
458
|
+
}
|
|
459
|
+
function button(label, type) {
|
|
460
|
+
const element = document.createElement("button");
|
|
461
|
+
element.type = type;
|
|
462
|
+
element.textContent = label;
|
|
463
|
+
return element;
|
|
464
|
+
}
|
|
465
|
+
function option(label, value) {
|
|
466
|
+
const element = document.createElement("option");
|
|
467
|
+
element.textContent = label;
|
|
468
|
+
element.value = value;
|
|
469
|
+
return element;
|
|
470
|
+
}
|
|
471
|
+
function placeholderOption(choices) {
|
|
472
|
+
let value = "";
|
|
473
|
+
while (choices.some((choice) => choice.value === value)) value += "\0";
|
|
474
|
+
const element = option("", value);
|
|
475
|
+
element.dataset.placeholder = "true";
|
|
476
|
+
return element;
|
|
477
|
+
}
|
|
478
|
+
function humanize(value) {
|
|
479
|
+
const words = value.replaceAll(/([a-z0-9])([A-Z])/g, "$1 $2").replaceAll(/[_-]+/g, " ");
|
|
480
|
+
return words ? `${words[0]?.toUpperCase() ?? ""}${words.slice(1)}` : value;
|
|
481
|
+
}
|
|
482
|
+
function record(value) {
|
|
483
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/interaction-card.ts
|
|
487
|
+
function createInteractionCard(options) {
|
|
488
|
+
const card = document.createElement("section");
|
|
489
|
+
card.className = "tool-proposal";
|
|
490
|
+
const label = document.createElement("strong");
|
|
491
|
+
label.textContent = options.tool;
|
|
492
|
+
card.append(label);
|
|
493
|
+
if (options.arguments && Object.keys(options.arguments).length > 0) {
|
|
494
|
+
card.append(createArgumentList(options.arguments));
|
|
495
|
+
}
|
|
496
|
+
const actions = document.createElement("div");
|
|
497
|
+
actions.className = "proposal-actions";
|
|
498
|
+
const buttons = [];
|
|
499
|
+
for (const [labelText, action] of [
|
|
500
|
+
[options.labels.accept, "accept"],
|
|
501
|
+
[options.labels.decline, "decline"],
|
|
502
|
+
[options.labels.cancel, "cancel"]
|
|
503
|
+
]) {
|
|
504
|
+
const button2 = document.createElement("button");
|
|
505
|
+
button2.type = "button";
|
|
506
|
+
button2.textContent = labelText;
|
|
507
|
+
buttons.push(button2);
|
|
508
|
+
button2.addEventListener("click", () => {
|
|
509
|
+
for (const candidate of buttons) candidate.disabled = true;
|
|
510
|
+
void options.respond(action).catch(() => {
|
|
511
|
+
for (const candidate of buttons) candidate.disabled = false;
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
actions.append(button2);
|
|
515
|
+
}
|
|
516
|
+
card.append(actions);
|
|
517
|
+
return card;
|
|
518
|
+
}
|
|
519
|
+
function createArgumentList(arguments_) {
|
|
520
|
+
const details = document.createElement("dl");
|
|
521
|
+
details.className = "proposal-arguments";
|
|
522
|
+
for (const [name, value] of Object.entries(arguments_)) {
|
|
523
|
+
const term = document.createElement("dt");
|
|
524
|
+
term.textContent = humanizeArgumentName(name);
|
|
525
|
+
const description = document.createElement("dd");
|
|
526
|
+
description.textContent = formatArgumentValue(value);
|
|
527
|
+
details.append(term, description);
|
|
528
|
+
}
|
|
529
|
+
return details;
|
|
530
|
+
}
|
|
531
|
+
function humanizeArgumentName(value) {
|
|
532
|
+
const words = value.replaceAll(/([a-z0-9])([A-Z])/g, "$1 $2").replaceAll(/[_-]+/g, " ");
|
|
533
|
+
return words ? `${words[0]?.toUpperCase() ?? ""}${words.slice(1)}` : value;
|
|
534
|
+
}
|
|
535
|
+
function formatArgumentValue(value) {
|
|
536
|
+
if (value === null) return "None";
|
|
537
|
+
if (typeof value === "string") return value;
|
|
538
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
539
|
+
return JSON.stringify(value) ?? "";
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/element-event-controller.ts
|
|
543
|
+
var AssistantElementEventController = class {
|
|
544
|
+
#host;
|
|
545
|
+
#assistantBody;
|
|
546
|
+
#completedTool;
|
|
547
|
+
#proposalCards = /* @__PURE__ */ new Map();
|
|
548
|
+
constructor(host) {
|
|
549
|
+
this.#host = host;
|
|
550
|
+
}
|
|
551
|
+
handle(event) {
|
|
552
|
+
if (event.event === "view_available") {
|
|
553
|
+
this.#host.element.dispatchEvent(
|
|
554
|
+
new CustomEvent("assistant-view-available", {
|
|
555
|
+
detail: event.data
|
|
556
|
+
})
|
|
557
|
+
);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (event.event === "unrecognized") return;
|
|
561
|
+
const { event: name, data } = event;
|
|
562
|
+
if (name === "session_started") {
|
|
563
|
+
if (isRecord(data.configuration)) {
|
|
564
|
+
this.#host.applyConfiguration(data.configuration);
|
|
565
|
+
}
|
|
566
|
+
this.#host.element.dispatchEvent(
|
|
567
|
+
new CustomEvent("assistant-session-started", {
|
|
568
|
+
detail: { expiresAt: String(data.expiresAt ?? "") }
|
|
569
|
+
})
|
|
570
|
+
);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (name === "session_expired") {
|
|
574
|
+
this.#host.element.dispatchEvent(new CustomEvent("assistant-session-expired"));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (name === "session_reset") {
|
|
578
|
+
this.resetConversation();
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (name === "context_changed") {
|
|
582
|
+
this.#host.element.dispatchEvent(
|
|
583
|
+
new CustomEvent("assistant-context-changed", { detail: { context: data.context } })
|
|
584
|
+
);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
if (name === "model_context_changed") {
|
|
588
|
+
this.#host.element.dispatchEvent(
|
|
589
|
+
new CustomEvent("assistant-model-context-changed", {
|
|
590
|
+
detail: { modelContext: data.modelContext }
|
|
591
|
+
})
|
|
592
|
+
);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (name === "message_started") {
|
|
596
|
+
this.#assistantBody = void 0;
|
|
597
|
+
this.#host.appendMessage("user", String(data.message ?? ""));
|
|
598
|
+
this.#host.element.dispatchEvent(new CustomEvent("assistant-message-started"));
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (name === "message_completed") {
|
|
602
|
+
this.#assistantBody = void 0;
|
|
603
|
+
this.#host.element.dispatchEvent(new CustomEvent("assistant-message-completed"));
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (name === "interaction_started") {
|
|
607
|
+
this.#assistantBody = void 0;
|
|
608
|
+
this.#completedTool = void 0;
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (name === "interaction_completed") {
|
|
612
|
+
this.#finishInteraction(String(data.id ?? ""), String(data.action ?? "accept"));
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (name === "content") {
|
|
616
|
+
const body = this.#assistantBody ?? this.#host.appendMessage("assistant", "");
|
|
617
|
+
this.#assistantBody = body;
|
|
618
|
+
if (body) body.textContent += String(data.delta ?? "");
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (name === "tool_proposed" || name === "interaction_proposed") {
|
|
622
|
+
this.#appendToolProposal(
|
|
623
|
+
String(data.id ?? ""),
|
|
624
|
+
String(data.tool ?? ("title" in data ? data.title : void 0) ?? "Action"),
|
|
625
|
+
isRecord(data.arguments) ? data.arguments : void 0
|
|
626
|
+
);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (name === "input_requested" && isRecord(data.requestedSchema)) {
|
|
630
|
+
this.#appendInputRequest(
|
|
631
|
+
String(data.id ?? ""),
|
|
632
|
+
String(data.message ?? "More information is required"),
|
|
633
|
+
data.requestedSchema
|
|
634
|
+
);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (name === "tool_completed") {
|
|
638
|
+
this.#completedTool = data;
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (name === "error") {
|
|
642
|
+
this.#host.dispatchError({
|
|
643
|
+
code: String(data.code ?? "stream_failed"),
|
|
644
|
+
...typeof data.status === "number" ? { status: data.status } : {},
|
|
645
|
+
retryable: data.retryable === true
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
resetConversation() {
|
|
650
|
+
this.#host.messages()?.replaceChildren();
|
|
651
|
+
this.#proposalCards.clear();
|
|
652
|
+
this.#assistantBody = void 0;
|
|
653
|
+
this.#completedTool = void 0;
|
|
654
|
+
this.#host.element.dispatchEvent(new CustomEvent("assistant-session-reset"));
|
|
655
|
+
}
|
|
656
|
+
#finishInteraction(id, action) {
|
|
657
|
+
if (this.#completedTool && !this.#assistantBody) {
|
|
658
|
+
this.#host.appendMessage("assistant", JSON.stringify(this.#completedTool.result ?? {}) ?? "");
|
|
659
|
+
}
|
|
660
|
+
this.#proposalCards.get(id)?.remove();
|
|
661
|
+
this.#proposalCards.delete(id);
|
|
662
|
+
if (action === "accept") {
|
|
663
|
+
this.#host.element.dispatchEvent(
|
|
664
|
+
new CustomEvent("assistant-tool-completed", { detail: { id } })
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
this.#host.element.dispatchEvent(
|
|
668
|
+
new CustomEvent("assistant-interaction-resolved", { detail: { id, action } })
|
|
669
|
+
);
|
|
670
|
+
this.#assistantBody = void 0;
|
|
671
|
+
this.#completedTool = void 0;
|
|
672
|
+
}
|
|
673
|
+
#appendToolProposal(id, tool, arguments_) {
|
|
674
|
+
const messages = this.#host.messages();
|
|
675
|
+
if (!messages || !id) return;
|
|
676
|
+
this.#proposalCards.get(id)?.remove();
|
|
677
|
+
const labels = this.#host.labels();
|
|
678
|
+
const card = createInteractionCard({
|
|
679
|
+
tool,
|
|
680
|
+
...arguments_ ? { arguments: arguments_ } : {},
|
|
681
|
+
labels: {
|
|
682
|
+
accept: labels.confirm,
|
|
683
|
+
decline: "Decline",
|
|
684
|
+
cancel: labels.cancel
|
|
685
|
+
},
|
|
686
|
+
respond: (action) => this.#host.respond(id, { action })
|
|
687
|
+
});
|
|
688
|
+
messages.append(card);
|
|
689
|
+
this.#proposalCards.set(id, card);
|
|
690
|
+
this.#host.element.dispatchEvent(
|
|
691
|
+
new CustomEvent("assistant-tool-proposed", { detail: { id, tool, arguments: arguments_ } })
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
#appendInputRequest(id, message, requestedSchema) {
|
|
695
|
+
const messages = this.#host.messages();
|
|
696
|
+
if (!messages || !id) return;
|
|
697
|
+
this.#proposalCards.get(id)?.remove();
|
|
698
|
+
const labels = this.#host.labels();
|
|
699
|
+
const card = createInputRequestCard({
|
|
700
|
+
message,
|
|
701
|
+
requestedSchema,
|
|
702
|
+
labels: {
|
|
703
|
+
submit: labels.confirm,
|
|
704
|
+
decline: "Decline",
|
|
705
|
+
cancel: labels.cancel
|
|
706
|
+
},
|
|
707
|
+
respond: (response) => this.#host.respond(id, response)
|
|
708
|
+
});
|
|
709
|
+
messages.append(card);
|
|
710
|
+
this.#proposalCards.set(id, card);
|
|
711
|
+
this.#host.element.dispatchEvent(
|
|
712
|
+
new CustomEvent("assistant-input-requested", {
|
|
713
|
+
detail: { id, message, requestedSchema }
|
|
714
|
+
})
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
function isRecord(value) {
|
|
719
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/element-styles.ts
|
|
723
|
+
var ASSISTANT_ELEMENT_STYLES = `<style>
|
|
724
|
+
:host { color-scheme: light dark; font-family: var(--ns-assistant-font-family, var(--ns-assistant-default-font-family)); font-size: var(--ns-assistant-base-font-size, var(--ns-assistant-default-base-font-size)); line-height: var(--ns-assistant-line-height, var(--ns-assistant-default-line-height)); z-index: var(--ns-assistant-z-index, var(--ns-assistant-default-z-index)); }
|
|
725
|
+
:host([data-mode="floating"]) { position: fixed; bottom: 24px; right: 24px; }
|
|
726
|
+
:host([data-mode="floating"][data-position="bottom-left"]) { right: auto; left: 24px; }
|
|
727
|
+
.launcher { width: 54px; height: 54px; border: 0; border-radius: var(--ns-assistant-launcher-radius, var(--ns-assistant-default-launcher-radius)); color: var(--ns-assistant-accent-text, var(--ns-assistant-default-accent-text)); background: var(--ns-assistant-accent, var(--ns-assistant-default-accent)); box-shadow: var(--ns-assistant-shadow, var(--ns-assistant-default-shadow)); cursor: pointer; }
|
|
728
|
+
.launcher img { width: 24px; height: 24px; object-fit: contain; }
|
|
729
|
+
.panel { display: none; width: min(var(--ns-assistant-panel-width, var(--ns-assistant-default-panel-width)), calc(100vw - 32px)); min-height: var(--ns-assistant-min-height, var(--ns-assistant-default-min-height)); max-height: min(var(--ns-assistant-max-height, var(--ns-assistant-default-max-height)), calc(100vh - 32px)); overflow: hidden; color: var(--ns-assistant-text, var(--ns-assistant-default-text)); background: var(--ns-assistant-panel, var(--ns-assistant-default-panel)); border-radius: var(--ns-assistant-panel-radius, var(--ns-assistant-default-panel-radius)); box-shadow: var(--ns-assistant-shadow, var(--ns-assistant-default-shadow)); }
|
|
730
|
+
:host([open]) .panel { display: grid; grid-template-rows: auto auto 1fr auto; }
|
|
731
|
+
:host([open]) .launcher { display: none; }
|
|
732
|
+
header { display: flex; gap: var(--ns-assistant-spacing, var(--ns-assistant-default-spacing)); align-items: center; padding: calc(var(--ns-assistant-spacing, var(--ns-assistant-default-spacing)) * 1.2); border-bottom: 1px solid var(--ns-assistant-divider, var(--ns-assistant-default-divider)); }
|
|
733
|
+
header strong { flex: 1; }
|
|
734
|
+
.brand-logo { display: block; max-width: 120px; max-height: 28px; object-fit: contain; }
|
|
735
|
+
button { font: inherit; }
|
|
736
|
+
.close, .send { border: 0; border-radius: var(--ns-assistant-button-radius, var(--ns-assistant-default-button-radius)); cursor: pointer; }
|
|
737
|
+
.close { color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); background: transparent; font-size: 24px; }
|
|
738
|
+
.welcome { padding: calc(var(--ns-assistant-spacing, var(--ns-assistant-default-spacing)) * 1.5); }
|
|
739
|
+
.welcome h2 { margin: 0; font-size: 1.25em; }
|
|
740
|
+
.welcome p { margin: 6px 0 0; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); }
|
|
741
|
+
.suggested-prompts { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 var(--ns-assistant-spacing, var(--ns-assistant-default-spacing)); }
|
|
742
|
+
.suggested-prompts:empty { display: none; }
|
|
743
|
+
.suggested-prompts button { padding: 7px 10px; color: var(--ns-assistant-text, var(--ns-assistant-default-text)); background: transparent; border: 1px solid var(--ns-assistant-divider, var(--ns-assistant-default-divider)); border-radius: 999px; cursor: pointer; }
|
|
744
|
+
.messages { min-height: 120px; padding: var(--ns-assistant-spacing, var(--ns-assistant-default-spacing)); overflow: auto; }
|
|
745
|
+
.message { width: fit-content; max-width: 84%; margin: 8px 0; padding: 10px 12px; border-radius: var(--ns-assistant-card-radius, var(--ns-assistant-default-card-radius)); background: var(--ns-assistant-elevated, var(--ns-assistant-default-elevated)); white-space: pre-wrap; }
|
|
746
|
+
.message .avatar { float: left; width: 24px; height: 24px; margin-right: 8px; border-radius: 50%; object-fit: cover; }
|
|
747
|
+
.message time { display: block; margin-top: 4px; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); font-size: .75em; }
|
|
748
|
+
.message.user { margin-left: auto; color: var(--ns-assistant-accent-text, var(--ns-assistant-default-accent-text)); background: var(--ns-assistant-accent, var(--ns-assistant-default-accent)); }
|
|
749
|
+
.tool-proposal { display: grid; gap: 10px; margin: 10px 0; padding: 12px; border-radius: var(--ns-assistant-card-radius, var(--ns-assistant-default-card-radius)); background: var(--ns-assistant-elevated, var(--ns-assistant-default-elevated)); }
|
|
750
|
+
.proposal-arguments { display: grid; grid-template-columns: minmax(90px, auto) 1fr; gap: 4px 10px; margin: 0; font-size: .9em; }
|
|
751
|
+
.proposal-arguments dt { color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); }
|
|
752
|
+
.proposal-arguments dd { margin: 0; overflow-wrap: anywhere; }
|
|
753
|
+
.proposal-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: end; }
|
|
754
|
+
.tool-proposal button { padding: 8px 10px; border: 0; border-radius: var(--ns-assistant-button-radius, var(--ns-assistant-default-button-radius)); color: var(--ns-assistant-text, var(--ns-assistant-default-text)); background: var(--ns-assistant-input, var(--ns-assistant-default-input)); cursor: pointer; }
|
|
755
|
+
.tool-proposal button:first-of-type { color: var(--ns-assistant-accent-text, var(--ns-assistant-default-accent-text)); background: var(--ns-assistant-accent, var(--ns-assistant-default-accent)); }
|
|
756
|
+
.input-request form { display: grid; gap: 10px; padding: 0; border: 0; }
|
|
757
|
+
.input-request label { display: grid; gap: 4px; color: var(--ns-assistant-muted-text, var(--ns-assistant-default-muted-text)); font-size: .9em; }
|
|
758
|
+
.input-request input, .input-request select { width: 100%; box-sizing: border-box; padding: 8px 10px; color: var(--ns-assistant-text, var(--ns-assistant-default-text)); background: var(--ns-assistant-input, var(--ns-assistant-default-input)); border: 1px solid var(--ns-assistant-divider, var(--ns-assistant-default-divider)); border-radius: var(--ns-assistant-input-radius, var(--ns-assistant-default-input-radius)); font: inherit; }
|
|
759
|
+
.input-request input[type="checkbox"] { width: auto; justify-self: start; }
|
|
760
|
+
form { display: flex; gap: 8px; align-items: end; padding: var(--ns-assistant-spacing, var(--ns-assistant-default-spacing)); border-top: 1px solid var(--ns-assistant-divider, var(--ns-assistant-default-divider)); }
|
|
761
|
+
textarea { flex: 1; resize: none; padding: 10px 12px; color: var(--ns-assistant-text, var(--ns-assistant-default-text)); background: var(--ns-assistant-input, var(--ns-assistant-default-input)); border: 0; border-radius: var(--ns-assistant-input-radius, var(--ns-assistant-default-input-radius)); font: inherit; }
|
|
762
|
+
textarea:focus-visible, button:focus-visible { outline: 3px solid var(--ns-assistant-focus, var(--ns-assistant-default-focus)); outline-offset: 2px; }
|
|
763
|
+
.send { padding: 10px 14px; color: var(--ns-assistant-accent-text, var(--ns-assistant-default-accent-text)); background: var(--ns-assistant-accent, var(--ns-assistant-default-accent)); }
|
|
764
|
+
.legal { display: flex; gap: 12px; justify-content: center; padding: 0 12px 10px; font-size: .8em; }
|
|
765
|
+
.legal:empty { display: none; }
|
|
766
|
+
.legal a { color: var(--ns-assistant-link, var(--ns-assistant-default-link)); }
|
|
767
|
+
:host([data-density="compact"]) .welcome { padding: var(--ns-assistant-spacing, var(--ns-assistant-default-spacing)); }
|
|
768
|
+
@media (max-width: 640px) { :host([mobile-fullscreen][open]) { inset: 0; } :host([mobile-fullscreen][open]) .panel { width: 100vw; min-height: 100vh; max-height: 100vh; border-radius: 0; } }
|
|
769
|
+
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; } }
|
|
770
|
+
@media (max-width: 560px) { :host([data-mode="floating"]) { inset: auto 12px 12px; } :host([open]) .panel { width: calc(100vw - 24px); max-height: calc(100vh - 24px); } }
|
|
771
|
+
</style>`;
|
|
772
|
+
|
|
773
|
+
// src/element.ts
|
|
774
|
+
var ASSISTANT_TAG_NAME = "noodle-assistant";
|
|
775
|
+
var HTMLElementBase = globalThis.HTMLElement ?? class {
|
|
776
|
+
};
|
|
777
|
+
var NoodleAssistantElement = class extends HTMLElementBase {
|
|
778
|
+
static observedAttributes = ["open", "theme"];
|
|
779
|
+
#appearance = resolveAppearance();
|
|
780
|
+
#client;
|
|
781
|
+
#clientEndpoint = "";
|
|
782
|
+
#unsubscribeClient;
|
|
783
|
+
#messages;
|
|
784
|
+
#media;
|
|
785
|
+
#context;
|
|
786
|
+
#modelContext;
|
|
787
|
+
sessionEndpoint = "";
|
|
788
|
+
#events = new AssistantElementEventController({
|
|
789
|
+
element: this,
|
|
790
|
+
messages: () => this.#messages,
|
|
791
|
+
appendMessage: (role, text) => this.#appendMessage(role, text),
|
|
792
|
+
applyConfiguration: (configuration) => {
|
|
793
|
+
this.#appearance = resolveAppearance(configuration);
|
|
794
|
+
this.#render();
|
|
795
|
+
},
|
|
796
|
+
labels: () => ({
|
|
797
|
+
confirm: this.#appearance.labels.confirm,
|
|
798
|
+
cancel: this.#appearance.labels.cancel
|
|
799
|
+
}),
|
|
800
|
+
respond: (id, response) => this.respond(id, response),
|
|
801
|
+
dispatchError: (detail) => this.#dispatchError(detail)
|
|
802
|
+
});
|
|
803
|
+
connectedCallback() {
|
|
804
|
+
if (!this.shadowRoot) this.attachShadow({ mode: "open" });
|
|
805
|
+
if (!this.sessionEndpoint) this.sessionEndpoint = this.getAttribute("session-endpoint") ?? "";
|
|
806
|
+
this.#media = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
|
|
807
|
+
this.#media?.addEventListener("change", this.#handleSystemTheme);
|
|
808
|
+
document.addEventListener("keydown", this.#handleDocumentKeydown);
|
|
809
|
+
document.addEventListener("pointerdown", this.#handleDocumentPointerDown);
|
|
810
|
+
if (this.#appearance.behavior.startOpen) this.setAttribute("open", "");
|
|
811
|
+
this.#render();
|
|
812
|
+
this.dispatchEvent(new CustomEvent("assistant-ready"));
|
|
813
|
+
}
|
|
814
|
+
disconnectedCallback() {
|
|
815
|
+
this.#client?.abort();
|
|
816
|
+
this.#unsubscribeClient?.();
|
|
817
|
+
this.#client = void 0;
|
|
818
|
+
this.#clientEndpoint = "";
|
|
819
|
+
this.#unsubscribeClient = void 0;
|
|
820
|
+
this.#media?.removeEventListener("change", this.#handleSystemTheme);
|
|
821
|
+
document.removeEventListener("keydown", this.#handleDocumentKeydown);
|
|
822
|
+
document.removeEventListener("pointerdown", this.#handleDocumentPointerDown);
|
|
823
|
+
}
|
|
824
|
+
attributeChangedCallback(name) {
|
|
825
|
+
if (name === "theme") this.#applyTheme();
|
|
826
|
+
}
|
|
827
|
+
open() {
|
|
828
|
+
this.setAttribute("open", "");
|
|
829
|
+
this.dispatchEvent(new CustomEvent("assistant-open"));
|
|
830
|
+
}
|
|
831
|
+
close() {
|
|
832
|
+
this.removeAttribute("open");
|
|
833
|
+
this.dispatchEvent(new CustomEvent("assistant-close"));
|
|
834
|
+
}
|
|
835
|
+
toggle() {
|
|
836
|
+
this.hasAttribute("open") ? this.close() : this.open();
|
|
837
|
+
}
|
|
838
|
+
focusComposer() {
|
|
839
|
+
this.shadowRoot?.querySelector("textarea")?.focus();
|
|
840
|
+
}
|
|
841
|
+
resetSession() {
|
|
842
|
+
if (this.#client) {
|
|
843
|
+
this.#client.resetSession();
|
|
844
|
+
} else {
|
|
845
|
+
this.#events.resetConversation();
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
updateContext(context) {
|
|
849
|
+
this.#context = { ...context };
|
|
850
|
+
if (this.#client) {
|
|
851
|
+
this.#client.updateContext(this.#context);
|
|
852
|
+
} else {
|
|
853
|
+
this.dispatchEvent(
|
|
854
|
+
new CustomEvent("assistant-context-changed", { detail: { context: this.#context } })
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
/** Replace the compact renderer summary attached to subsequent message turns. */
|
|
859
|
+
updateModelContext(update) {
|
|
860
|
+
this.#modelContext = copyAssistantModelContext(update);
|
|
861
|
+
if (this.#client) {
|
|
862
|
+
this.#client.updateModelContext(this.#modelContext);
|
|
863
|
+
} else {
|
|
864
|
+
this.dispatchEvent(
|
|
865
|
+
new CustomEvent("assistant-model-context-changed", {
|
|
866
|
+
detail: { modelContext: this.#modelContext }
|
|
867
|
+
})
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
async sendMessage(text) {
|
|
872
|
+
const message = text.trim();
|
|
873
|
+
if (!message) return;
|
|
874
|
+
try {
|
|
875
|
+
await this.#ensureClient().sendMessage(message);
|
|
876
|
+
} catch (error) {
|
|
877
|
+
this.#dispatchClientError(error, "turn_failed");
|
|
878
|
+
throw error;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Every listener the element attaches to its own DOM must go through this guard: internal UI
|
|
883
|
+
* failures surface as `assistant-error` events (already dispatched by the failing path) and must
|
|
884
|
+
* never escape as unhandled rejections into the embedding page. Programmatic callers use
|
|
885
|
+
* `sendMessage`/`confirmTool` directly and keep the rejection.
|
|
886
|
+
*/
|
|
887
|
+
#sendGuarded(text) {
|
|
888
|
+
void this.sendMessage(text).catch(() => {
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
async confirmTool(id) {
|
|
892
|
+
await this.respond(id, { action: "accept" });
|
|
893
|
+
}
|
|
894
|
+
async respond(id, response) {
|
|
895
|
+
try {
|
|
896
|
+
await this.#ensureClient().respond(id, response);
|
|
897
|
+
} catch (error) {
|
|
898
|
+
this.#dispatchClientError(error, "confirmation_failed");
|
|
899
|
+
throw error;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
#handleSystemTheme = () => this.#applyTheme();
|
|
903
|
+
#handleDocumentKeydown = (event) => {
|
|
904
|
+
if (event.key === "Escape" && this.hasAttribute("open") && this.#appearance.behavior.closeOnEscape) {
|
|
905
|
+
this.close();
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
#handleDocumentPointerDown = (event) => {
|
|
909
|
+
if (this.hasAttribute("open") && this.#appearance.behavior.closeOnOutsideClick && !event.composedPath().includes(this)) {
|
|
910
|
+
this.close();
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
#ensureClient() {
|
|
914
|
+
if (this.#client && this.#clientEndpoint === this.sessionEndpoint) return this.#client;
|
|
915
|
+
this.#client?.abort();
|
|
916
|
+
this.#unsubscribeClient?.();
|
|
917
|
+
const client = createAssistantClient({
|
|
918
|
+
sessionEndpoint: this.sessionEndpoint,
|
|
919
|
+
...this.#context ? { context: this.#context } : {},
|
|
920
|
+
clientContext: browserClientContext,
|
|
921
|
+
...this.#modelContext ? { modelContext: this.#modelContext } : {}
|
|
922
|
+
});
|
|
923
|
+
this.#client = client;
|
|
924
|
+
this.#clientEndpoint = this.sessionEndpoint;
|
|
925
|
+
this.#unsubscribeClient = client.subscribe((event) => this.#events.handle(event));
|
|
926
|
+
return client;
|
|
927
|
+
}
|
|
928
|
+
#appendMessage(role, text) {
|
|
929
|
+
if (!this.#messages) return void 0;
|
|
930
|
+
const message = document.createElement("div");
|
|
931
|
+
message.className = `message ${role}`;
|
|
932
|
+
if (role === "assistant" && this.#appearance.behavior.showAvatars && this.#appearance.brand.avatar[this.#resolvedMode()]) {
|
|
933
|
+
const avatar = document.createElement("img");
|
|
934
|
+
avatar.className = "avatar";
|
|
935
|
+
avatar.src = this.#appearance.brand.avatar[this.#resolvedMode()] ?? "";
|
|
936
|
+
avatar.alt = "";
|
|
937
|
+
message.append(avatar);
|
|
938
|
+
}
|
|
939
|
+
const body = document.createElement("span");
|
|
940
|
+
body.textContent = text;
|
|
941
|
+
message.append(body);
|
|
942
|
+
if (this.#appearance.behavior.showTimestamps) {
|
|
943
|
+
const timestamp = document.createElement("time");
|
|
944
|
+
timestamp.dateTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
945
|
+
timestamp.textContent = new Intl.DateTimeFormat(this.#appearance.locale, {
|
|
946
|
+
hour: "numeric",
|
|
947
|
+
minute: "2-digit"
|
|
948
|
+
}).format(/* @__PURE__ */ new Date());
|
|
949
|
+
message.append(timestamp);
|
|
950
|
+
}
|
|
951
|
+
this.#messages.append(message);
|
|
952
|
+
return body;
|
|
953
|
+
}
|
|
954
|
+
#dispatchError(detail) {
|
|
955
|
+
this.dispatchEvent(new CustomEvent("assistant-error", { detail }));
|
|
956
|
+
}
|
|
957
|
+
#dispatchClientError(error, fallbackCode) {
|
|
958
|
+
this.#dispatchError(
|
|
959
|
+
error instanceof AssistantClientError ? error.detail : { code: fallbackCode, retryable: false }
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
#resolvedMode() {
|
|
963
|
+
const configured = this.getAttribute("theme") ?? this.#appearance.theme.defaultMode;
|
|
964
|
+
return configured === "auto" ? this.#media?.matches ? "dark" : "light" : configured;
|
|
965
|
+
}
|
|
966
|
+
#applyTheme() {
|
|
967
|
+
const mode = this.#resolvedMode();
|
|
968
|
+
this.setAttribute("data-theme", mode);
|
|
969
|
+
const tokens = this.#appearance.theme[mode];
|
|
970
|
+
for (const [name, value] of Object.entries(tokens)) {
|
|
971
|
+
this.style.setProperty(
|
|
972
|
+
`--ns-assistant-default-${camelToKebab(name)}`,
|
|
973
|
+
tokenValue(name, value)
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
this.style.setProperty(
|
|
977
|
+
"--ns-assistant-default-panel-width",
|
|
978
|
+
`${this.#appearance.layout.panelWidth}px`
|
|
979
|
+
);
|
|
980
|
+
this.style.setProperty(
|
|
981
|
+
"--ns-assistant-default-min-height",
|
|
982
|
+
`${this.#appearance.layout.panelMinHeight}px`
|
|
983
|
+
);
|
|
984
|
+
this.style.setProperty(
|
|
985
|
+
"--ns-assistant-default-max-height",
|
|
986
|
+
`${this.#appearance.layout.panelMaxHeight}px`
|
|
987
|
+
);
|
|
988
|
+
this.style.setProperty(
|
|
989
|
+
"--ns-assistant-default-z-index",
|
|
990
|
+
String(this.#appearance.layout.zIndex)
|
|
991
|
+
);
|
|
992
|
+
const logo = this.shadowRoot?.querySelector(".brand-logo");
|
|
993
|
+
const logoUrl = this.#appearance.brand.logo[mode];
|
|
994
|
+
if (logo) {
|
|
995
|
+
logo.hidden = !logoUrl;
|
|
996
|
+
if (logoUrl) logo.src = logoUrl;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
#render() {
|
|
1000
|
+
if (!this.shadowRoot) return;
|
|
1001
|
+
const conversation = this.#messages ? [...this.#messages.childNodes] : [];
|
|
1002
|
+
const appearance = this.#appearance;
|
|
1003
|
+
this.setAttribute("role", "complementary");
|
|
1004
|
+
this.setAttribute("aria-label", appearance.brand.name);
|
|
1005
|
+
this.setAttribute("lang", appearance.locale);
|
|
1006
|
+
this.setAttribute("dir", appearance.direction);
|
|
1007
|
+
this.dataset.mode = appearance.layout.mode;
|
|
1008
|
+
this.dataset.position = appearance.layout.position;
|
|
1009
|
+
this.dataset.density = appearance.layout.density;
|
|
1010
|
+
this.toggleAttribute("mobile-fullscreen", appearance.layout.mobileFullscreen);
|
|
1011
|
+
this.shadowRoot.innerHTML = `${ASSISTANT_ELEMENT_STYLES}
|
|
1012
|
+
<button class="launcher" type="button" aria-label="${escapeAttribute(appearance.labels.open)}"><slot name="launcher-icon">\u2726</slot></button>
|
|
1013
|
+
<section class="panel" aria-label="${escapeAttribute(appearance.brand.name)}">
|
|
1014
|
+
<header><slot name="header-leading"></slot><img class="brand-logo" alt=""><strong></strong><slot name="header-actions"></slot><button class="close" type="button" aria-label="${escapeAttribute(appearance.labels.close)}">\xD7</button></header>
|
|
1015
|
+
<div class="welcome"><slot name="empty-state"><h2></h2><p></p></slot></div>
|
|
1016
|
+
<div class="suggested-prompts"></div>
|
|
1017
|
+
<div class="messages" aria-live="polite"></div>
|
|
1018
|
+
<form><slot name="composer-leading"></slot><textarea rows="1"></textarea><button class="send" type="submit"></button><slot name="composer-trailing"></slot></form>
|
|
1019
|
+
<nav class="legal" aria-label="Legal"></nav>
|
|
1020
|
+
<slot name="conversation-footer"></slot>
|
|
1021
|
+
</section>`;
|
|
1022
|
+
queryRequired(this.shadowRoot, "strong").textContent = appearance.brand.name;
|
|
1023
|
+
const launcherIcon = appearance.brand.mark[this.#resolvedMode()];
|
|
1024
|
+
if (launcherIcon) {
|
|
1025
|
+
const launcherSlot = queryRequired(
|
|
1026
|
+
this.shadowRoot,
|
|
1027
|
+
'slot[name="launcher-icon"]'
|
|
1028
|
+
);
|
|
1029
|
+
const icon = document.createElement("img");
|
|
1030
|
+
icon.src = launcherIcon;
|
|
1031
|
+
icon.alt = "";
|
|
1032
|
+
launcherSlot.replaceChildren(icon);
|
|
1033
|
+
}
|
|
1034
|
+
const logo = queryRequired(this.shadowRoot, ".brand-logo");
|
|
1035
|
+
const logoUrl = appearance.brand.logo[this.#resolvedMode()];
|
|
1036
|
+
if (logoUrl) {
|
|
1037
|
+
logo.src = logoUrl;
|
|
1038
|
+
logo.alt = appearance.brand.name;
|
|
1039
|
+
} else {
|
|
1040
|
+
logo.hidden = true;
|
|
1041
|
+
}
|
|
1042
|
+
queryRequired(this.shadowRoot, "h2").textContent = appearance.labels.welcomeHeading;
|
|
1043
|
+
queryRequired(this.shadowRoot, "p").textContent = appearance.labels.welcomeMessage;
|
|
1044
|
+
const textarea = queryRequired(this.shadowRoot, "textarea");
|
|
1045
|
+
textarea.placeholder = appearance.labels.composerPlaceholder;
|
|
1046
|
+
textarea.setAttribute("aria-label", appearance.labels.composerPlaceholder);
|
|
1047
|
+
queryRequired(this.shadowRoot, ".send").textContent = appearance.labels.send;
|
|
1048
|
+
const prompts = queryRequired(this.shadowRoot, ".suggested-prompts");
|
|
1049
|
+
for (const prompt of appearance.suggestedPrompts) {
|
|
1050
|
+
const button2 = document.createElement("button");
|
|
1051
|
+
button2.type = "button";
|
|
1052
|
+
button2.textContent = prompt;
|
|
1053
|
+
button2.addEventListener("click", () => this.#sendGuarded(prompt));
|
|
1054
|
+
prompts.append(button2);
|
|
1055
|
+
}
|
|
1056
|
+
if (!appearance.behavior.showLauncher)
|
|
1057
|
+
queryRequired(this.shadowRoot, ".launcher").hidden = true;
|
|
1058
|
+
if (!appearance.behavior.showHeader)
|
|
1059
|
+
queryRequired(this.shadowRoot, "header").hidden = true;
|
|
1060
|
+
const legal = queryRequired(this.shadowRoot, ".legal");
|
|
1061
|
+
for (const [label, href] of [
|
|
1062
|
+
["Privacy", appearance.privacyUrl],
|
|
1063
|
+
["Terms", appearance.termsUrl]
|
|
1064
|
+
]) {
|
|
1065
|
+
if (!href) continue;
|
|
1066
|
+
const link = document.createElement("a");
|
|
1067
|
+
link.href = href;
|
|
1068
|
+
link.target = "_blank";
|
|
1069
|
+
link.rel = "noopener noreferrer";
|
|
1070
|
+
link.textContent = label;
|
|
1071
|
+
legal.append(link);
|
|
1072
|
+
}
|
|
1073
|
+
this.#messages = this.shadowRoot.querySelector(".messages") ?? void 0;
|
|
1074
|
+
this.#messages?.append(...conversation);
|
|
1075
|
+
this.shadowRoot.querySelector(".launcher")?.addEventListener("click", () => this.open());
|
|
1076
|
+
this.shadowRoot.querySelector(".close")?.addEventListener("click", () => this.close());
|
|
1077
|
+
this.shadowRoot.querySelector("form")?.addEventListener("submit", (event) => {
|
|
1078
|
+
event.preventDefault();
|
|
1079
|
+
const value = textarea.value;
|
|
1080
|
+
textarea.value = "";
|
|
1081
|
+
this.#sendGuarded(value);
|
|
1082
|
+
});
|
|
1083
|
+
this.#applyTheme();
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
function registerNoodleAssistant() {
|
|
1087
|
+
if (globalThis.customElements && !customElements.get(ASSISTANT_TAG_NAME)) {
|
|
1088
|
+
customElements.define(ASSISTANT_TAG_NAME, NoodleAssistantElement);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
function camelToKebab(value) {
|
|
1092
|
+
return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
1093
|
+
}
|
|
1094
|
+
function tokenValue(name, value) {
|
|
1095
|
+
if (typeof value !== "number") return value;
|
|
1096
|
+
if (name === "lineHeight" || name === "motionScale") return String(value);
|
|
1097
|
+
return `${value}px`;
|
|
1098
|
+
}
|
|
1099
|
+
function escapeAttribute(value) {
|
|
1100
|
+
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<");
|
|
1101
|
+
}
|
|
1102
|
+
function queryRequired(root, selector) {
|
|
1103
|
+
const element = root.querySelector(selector);
|
|
1104
|
+
if (!element) throw new Error(`assistant renderer is missing ${selector}`);
|
|
1105
|
+
return element;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
export {
|
|
1109
|
+
ASSISTANT_TAG_NAME,
|
|
1110
|
+
NoodleAssistantElement,
|
|
1111
|
+
registerNoodleAssistant
|
|
1112
|
+
};
|
|
1113
|
+
//# sourceMappingURL=chunk-J2YDKISA.js.map
|