@artooi/ag-ui-web-component 0.6.0 → 0.7.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/CHANGELOG.md +44 -1
- package/README.md +140 -6
- package/dist/ag-ui-web-component.bundle.js +145 -47
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/core/ag_ui_chat.d.ts +16 -0
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +16 -0
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +672 -228
- package/dist/index.js.map +3 -3
- package/dist/tools/page_action_tools.d.ts +31 -0
- package/dist/tools/page_action_tools.d.ts.map +1 -0
- package/dist/ui/attachment_tray.d.ts +3 -0
- package/dist/ui/attachment_tray.d.ts.map +1 -1
- package/dist/ui/confirmation_card.d.ts +4 -1
- package/dist/ui/confirmation_card.d.ts.map +1 -1
- package/dist/ui/relative_time.d.ts +5 -3
- package/dist/ui/relative_time.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/thread_drawer.d.ts +7 -1
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/tool_call_card.d.ts +6 -2
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +126 -0
- package/dist/ui/ui_strings.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/core/ag_ui_chat.ts +213 -41
- package/src/core/agui_client.ts +33 -2
- package/src/index.ts +7 -0
- package/src/tools/page_action_tools.ts +130 -0
- package/src/ui/attachment_tray.ts +13 -7
- package/src/ui/confirmation_card.ts +15 -5
- package/src/ui/relative_time.ts +15 -8
- package/src/ui/styles.ts +98 -0
- package/src/ui/thread_drawer.ts +53 -25
- package/src/ui/tool_call_card.ts +40 -17
- package/src/ui/ui_strings.ts +208 -0
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -104,6 +104,211 @@ function isNavigates(parameters) {
|
|
|
104
104
|
return parameters[X_NAVIGATES_KEY] === true;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// src/dom/native_setter.ts
|
|
108
|
+
function prototypeSetter(proto, prop) {
|
|
109
|
+
const setter = Object.getOwnPropertyDescriptor(proto, prop).set;
|
|
110
|
+
return setter;
|
|
111
|
+
}
|
|
112
|
+
var setInputValue = prototypeSetter(HTMLInputElement.prototype, "value");
|
|
113
|
+
var setTextareaValue = prototypeSetter(HTMLTextAreaElement.prototype, "value");
|
|
114
|
+
var setSelectValue = prototypeSetter(HTMLSelectElement.prototype, "value");
|
|
115
|
+
var setInputChecked = prototypeSetter(HTMLInputElement.prototype, "checked");
|
|
116
|
+
function setNativeValue(el, value) {
|
|
117
|
+
if (el instanceof HTMLTextAreaElement) {
|
|
118
|
+
setTextareaValue.call(el, value);
|
|
119
|
+
} else if (el instanceof HTMLSelectElement) {
|
|
120
|
+
setSelectValue.call(el, value);
|
|
121
|
+
} else {
|
|
122
|
+
setInputValue.call(el, value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function setNativeChecked(el, checked) {
|
|
126
|
+
setInputChecked.call(el, checked);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/dom/animations.ts
|
|
130
|
+
var ACCENT = "#4f46e5";
|
|
131
|
+
function delay(ms) {
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
setTimeout(resolve, ms);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async function typeInto(el, value, options = {}) {
|
|
137
|
+
const charDelayMs = options.charDelayMs ?? 35;
|
|
138
|
+
setNativeValue(el, "");
|
|
139
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
140
|
+
for (const char of value) {
|
|
141
|
+
setNativeValue(el, el.value + char);
|
|
142
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
143
|
+
if (charDelayMs > 0) {
|
|
144
|
+
await delay(charDelayMs);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
148
|
+
}
|
|
149
|
+
async function highlightThenClick(el, options = {}) {
|
|
150
|
+
const highlightMs = options.highlightMs ?? 280;
|
|
151
|
+
const previousOutline = el.style.outline;
|
|
152
|
+
const previousOffset = el.style.outlineOffset;
|
|
153
|
+
el.style.outline = `2px solid ${ACCENT}`;
|
|
154
|
+
el.style.outlineOffset = "2px";
|
|
155
|
+
await delay(highlightMs);
|
|
156
|
+
el.style.outline = previousOutline;
|
|
157
|
+
el.style.outlineOffset = previousOffset;
|
|
158
|
+
el.click();
|
|
159
|
+
}
|
|
160
|
+
function scrollIntoCenterView(el) {
|
|
161
|
+
el.scrollIntoView({ block: "center", inline: "nearest", behavior: "smooth" });
|
|
162
|
+
}
|
|
163
|
+
async function focusWithFlash(el, options = {}) {
|
|
164
|
+
const flashMs = options.flashMs ?? 200;
|
|
165
|
+
el.focus();
|
|
166
|
+
const previousShadow = el.style.boxShadow;
|
|
167
|
+
el.style.boxShadow = `0 0 0 3px rgba(79, 70, 229, 0.4)`;
|
|
168
|
+
await delay(flashMs);
|
|
169
|
+
el.style.boxShadow = previousShadow;
|
|
170
|
+
}
|
|
171
|
+
var RING = "0 0 0 3px rgba(79, 70, 229, 0.4)";
|
|
172
|
+
function prefersReducedMotion() {
|
|
173
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
174
|
+
}
|
|
175
|
+
function motionDelay(ms) {
|
|
176
|
+
if (ms <= 0 || prefersReducedMotion()) {
|
|
177
|
+
return Promise.resolve();
|
|
178
|
+
}
|
|
179
|
+
return delay(ms);
|
|
180
|
+
}
|
|
181
|
+
async function pressThenClick(el, options = {}) {
|
|
182
|
+
const pressMs = options.pressMs ?? 140;
|
|
183
|
+
const previousTransform = el.style.transform;
|
|
184
|
+
const previousTransition = el.style.transition;
|
|
185
|
+
const previousShadow = el.style.boxShadow;
|
|
186
|
+
el.style.transition = "transform 80ms ease";
|
|
187
|
+
el.style.transform = "scale(0.96)";
|
|
188
|
+
el.style.boxShadow = RING;
|
|
189
|
+
await motionDelay(pressMs);
|
|
190
|
+
el.style.transform = previousTransform;
|
|
191
|
+
el.style.transition = previousTransition;
|
|
192
|
+
el.style.boxShadow = previousShadow;
|
|
193
|
+
el.click();
|
|
194
|
+
}
|
|
195
|
+
function findOption(el, value) {
|
|
196
|
+
for (const option of Array.from(el.options)) {
|
|
197
|
+
if (option.value === value || option.text === value) {
|
|
198
|
+
return option;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
async function selectOption(el, value, options = {}) {
|
|
204
|
+
const option = findOption(el, value);
|
|
205
|
+
if (option === null) {
|
|
206
|
+
throw new Error(`no <option> matching "${value}"`);
|
|
207
|
+
}
|
|
208
|
+
const highlightMs = options.highlightMs ?? 220;
|
|
209
|
+
const previousOutline = el.style.outline;
|
|
210
|
+
const previousOffset = el.style.outlineOffset;
|
|
211
|
+
el.style.outline = `2px solid ${ACCENT}`;
|
|
212
|
+
el.style.outlineOffset = "2px";
|
|
213
|
+
await motionDelay(highlightMs);
|
|
214
|
+
setNativeValue(el, option.value);
|
|
215
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
216
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
217
|
+
el.style.outline = previousOutline;
|
|
218
|
+
el.style.outlineOffset = previousOffset;
|
|
219
|
+
}
|
|
220
|
+
async function toggleControl(el, checked, options = {}) {
|
|
221
|
+
const flashMs = options.flashMs ?? 200;
|
|
222
|
+
const previousShadow = el.style.boxShadow;
|
|
223
|
+
el.style.boxShadow = RING;
|
|
224
|
+
await motionDelay(flashMs);
|
|
225
|
+
setNativeChecked(el, checked);
|
|
226
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
227
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
228
|
+
el.style.boxShadow = previousShadow;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/tools/page_action_tools.ts
|
|
232
|
+
var PAGE_ACTIONS = {
|
|
233
|
+
SCROLL: "scroll",
|
|
234
|
+
DRAG: "drag"
|
|
235
|
+
};
|
|
236
|
+
function createPageActionTools(enabled, resolveTarget) {
|
|
237
|
+
const tools = [];
|
|
238
|
+
if (enabled.has(PAGE_ACTIONS.SCROLL)) {
|
|
239
|
+
tools.push(scrollTool(resolveTarget));
|
|
240
|
+
}
|
|
241
|
+
if (enabled.has(PAGE_ACTIONS.DRAG)) {
|
|
242
|
+
tools.push(dragTool(resolveTarget));
|
|
243
|
+
}
|
|
244
|
+
return tools;
|
|
245
|
+
}
|
|
246
|
+
function scrollTool(resolveTarget) {
|
|
247
|
+
return {
|
|
248
|
+
name: "scroll_to",
|
|
249
|
+
description: "Scroll a target into view. `target` is `top`, `bottom`, or a CSS selector / page-map element id. Read-only: it changes nothing on the page.",
|
|
250
|
+
parameters: {
|
|
251
|
+
type: "object",
|
|
252
|
+
properties: { target: { type: "string" } },
|
|
253
|
+
required: ["target"],
|
|
254
|
+
[X_SUMMARY_KEY]: "Scroll into view"
|
|
255
|
+
},
|
|
256
|
+
handler: (args) => {
|
|
257
|
+
const target = String(args["target"] ?? "");
|
|
258
|
+
if (target === "top" || target === "bottom") {
|
|
259
|
+
const top = target === "top" ? 0 : document.body.scrollHeight;
|
|
260
|
+
window.scrollTo({ top, behavior: "smooth" });
|
|
261
|
+
return { scrolled: true, target };
|
|
262
|
+
}
|
|
263
|
+
const element = resolveTarget(target);
|
|
264
|
+
if (element === null) {
|
|
265
|
+
throw new Error(`no element matching "${target}"`);
|
|
266
|
+
}
|
|
267
|
+
scrollIntoCenterView(element);
|
|
268
|
+
return { scrolled: true, target };
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function dragTool(resolveTarget) {
|
|
273
|
+
return {
|
|
274
|
+
name: "drag_and_drop",
|
|
275
|
+
description: "Drag the `from` element onto the `to` element (CSS selectors or page-map element ids), firing the page's native drag-and-drop. Use for reordering sortable lists. The page decides what the drop commits.",
|
|
276
|
+
parameters: {
|
|
277
|
+
type: "object",
|
|
278
|
+
properties: { from: { type: "string" }, to: { type: "string" } },
|
|
279
|
+
required: ["from", "to"],
|
|
280
|
+
[X_SUMMARY_KEY]: "Drag and drop"
|
|
281
|
+
},
|
|
282
|
+
handler: (args) => {
|
|
283
|
+
const fromTarget = String(args["from"] ?? "");
|
|
284
|
+
const toTarget = String(args["to"] ?? "");
|
|
285
|
+
const from = resolveTarget(fromTarget);
|
|
286
|
+
if (from === null) {
|
|
287
|
+
throw new Error(`no element matching "${fromTarget}"`);
|
|
288
|
+
}
|
|
289
|
+
const to = resolveTarget(toTarget);
|
|
290
|
+
if (to === null) {
|
|
291
|
+
throw new Error(`no element matching "${toTarget}"`);
|
|
292
|
+
}
|
|
293
|
+
dispatchDragSequence(from, to);
|
|
294
|
+
return { dragged: true, from: fromTarget, to: toTarget };
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function dispatchDragSequence(from, to) {
|
|
299
|
+
const dataTransfer = new DataTransfer();
|
|
300
|
+
fire(from, "dragstart", dataTransfer);
|
|
301
|
+
fire(to, "dragenter", dataTransfer);
|
|
302
|
+
fire(to, "dragover", dataTransfer);
|
|
303
|
+
fire(to, "drop", dataTransfer);
|
|
304
|
+
fire(from, "dragend", dataTransfer);
|
|
305
|
+
}
|
|
306
|
+
function fire(target, type, dataTransfer) {
|
|
307
|
+
const event = new Event(type, { bubbles: true, cancelable: true });
|
|
308
|
+
event.dataTransfer = dataTransfer;
|
|
309
|
+
target.dispatchEvent(event);
|
|
310
|
+
}
|
|
311
|
+
|
|
107
312
|
// src/tools/page_map.ts
|
|
108
313
|
function createPageMapContext(getPageMap, autoInject) {
|
|
109
314
|
if (!autoInject || getPageMap === null) {
|
|
@@ -292,15 +497,83 @@ function formatBytes(bytes) {
|
|
|
292
497
|
|
|
293
498
|
// src/ui/attachment_tray.ts
|
|
294
499
|
import { randomUUID } from "@ag-ui/client";
|
|
500
|
+
|
|
501
|
+
// src/ui/ui_strings.ts
|
|
502
|
+
var DEFAULT_UI_STRINGS = {
|
|
503
|
+
title: "Assistant",
|
|
504
|
+
chatHistory: "Chat history",
|
|
505
|
+
newChat: "New chat",
|
|
506
|
+
collapse: "Collapse",
|
|
507
|
+
expand: "Expand",
|
|
508
|
+
conversation: "Conversation",
|
|
509
|
+
thinking: "Assistant is thinking\u2026",
|
|
510
|
+
stopped: "\u23F9 Stopped",
|
|
511
|
+
connectionLost: "Connection lost",
|
|
512
|
+
noResult: "No result returned.",
|
|
513
|
+
declinedAction: "User declined the action.",
|
|
514
|
+
navigating: "Navigating\u2026",
|
|
515
|
+
skillNeeds: "\u201C{title}\u201D needs: {fields}",
|
|
516
|
+
message: "Message",
|
|
517
|
+
inputPlaceholder: "Ask anything\u2026",
|
|
518
|
+
send: "Send",
|
|
519
|
+
stop: "Stop",
|
|
520
|
+
attachFiles: "Attach files",
|
|
521
|
+
toolRunning: "running\u2026",
|
|
522
|
+
toolDone: "\u2713 done",
|
|
523
|
+
toolError: "\u26A0 error",
|
|
524
|
+
toolDeclined: "\u2298 declined",
|
|
525
|
+
resultLabel: "Result",
|
|
526
|
+
errorLabel: "Error",
|
|
527
|
+
declinedLabel: "Declined",
|
|
528
|
+
details: "Details",
|
|
529
|
+
confirmAction: "Confirm action",
|
|
530
|
+
confirmRun: "Run \u201C{tool}\u201D?",
|
|
531
|
+
confirm: "Confirm",
|
|
532
|
+
cancel: "Cancel",
|
|
533
|
+
chats: "Chats",
|
|
534
|
+
noConversations: "No conversations yet.",
|
|
535
|
+
rename: "Rename",
|
|
536
|
+
renameConversation: "Rename conversation",
|
|
537
|
+
delete: "Delete",
|
|
538
|
+
deleteConversation: "Delete conversation",
|
|
539
|
+
deletePrompt: "Delete?",
|
|
540
|
+
tooLarge: "Too large (max {size})",
|
|
541
|
+
fileTypeNotAllowed: "File type not allowed",
|
|
542
|
+
uploadFailed: "upload failed",
|
|
543
|
+
retry: "Retry",
|
|
544
|
+
retryUpload: "Retry upload",
|
|
545
|
+
remove: "Remove",
|
|
546
|
+
removeAttachment: "Remove attachment",
|
|
547
|
+
justNow: "just now",
|
|
548
|
+
minutesAgo: "{n}m ago",
|
|
549
|
+
hoursAgo: "{n}h ago",
|
|
550
|
+
daysAgo: "{n}d ago",
|
|
551
|
+
weeksAgo: "{n}w ago"
|
|
552
|
+
};
|
|
553
|
+
function mergeUiStrings(overrides) {
|
|
554
|
+
const merged = { ...DEFAULT_UI_STRINGS };
|
|
555
|
+
for (const key of Object.keys(overrides)) {
|
|
556
|
+
const value = overrides[key];
|
|
557
|
+
if (value !== void 0) {
|
|
558
|
+
merged[key] = value;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return merged;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/ui/attachment_tray.ts
|
|
295
565
|
var AttachmentTray = class {
|
|
296
566
|
/** The tray root; append above the input row. Hidden while empty. */
|
|
297
567
|
element;
|
|
298
568
|
#config;
|
|
569
|
+
#strings;
|
|
299
570
|
#items = [];
|
|
300
571
|
constructor(config) {
|
|
301
572
|
this.#config = config;
|
|
573
|
+
this.#strings = config.strings ?? DEFAULT_UI_STRINGS;
|
|
302
574
|
this.element = document.createElement("div");
|
|
303
575
|
this.element.className = "attachment-tray";
|
|
576
|
+
this.element.setAttribute("part", "attachment-tray");
|
|
304
577
|
this.element.hidden = true;
|
|
305
578
|
}
|
|
306
579
|
/** Queue a file: reject oversize/disallowed into an error chip, else upload. */
|
|
@@ -357,10 +630,10 @@ var AttachmentTray = class {
|
|
|
357
630
|
/** The size/type rejection reason for a file, or `null` when accepted. */
|
|
358
631
|
#reject(file) {
|
|
359
632
|
if (this.#config.maxBytes > 0 && file.size > this.#config.maxBytes) {
|
|
360
|
-
return
|
|
633
|
+
return this.#strings.tooLarge.replace("{size}", formatBytes(this.#config.maxBytes));
|
|
361
634
|
}
|
|
362
635
|
if (!accepts(this.#config.accept, file)) {
|
|
363
|
-
return
|
|
636
|
+
return this.#strings.fileTypeNotAllowed;
|
|
364
637
|
}
|
|
365
638
|
return null;
|
|
366
639
|
}
|
|
@@ -377,7 +650,7 @@ var AttachmentTray = class {
|
|
|
377
650
|
item.ref = ref;
|
|
378
651
|
}).catch((error) => {
|
|
379
652
|
item.status = ATTACHMENT_STATUS.ERROR;
|
|
380
|
-
item.error = error instanceof Error ? error.message :
|
|
653
|
+
item.error = error instanceof Error ? error.message : this.#strings.uploadFailed;
|
|
381
654
|
}).finally(() => {
|
|
382
655
|
this.#render();
|
|
383
656
|
this.#config.onChange?.();
|
|
@@ -423,8 +696,8 @@ var AttachmentTray = class {
|
|
|
423
696
|
const retry = document.createElement("button");
|
|
424
697
|
retry.type = "button";
|
|
425
698
|
retry.className = "attachment-chip-retry";
|
|
426
|
-
retry.title =
|
|
427
|
-
retry.setAttribute("aria-label",
|
|
699
|
+
retry.title = this.#strings.retry;
|
|
700
|
+
retry.setAttribute("aria-label", this.#strings.retryUpload);
|
|
428
701
|
retry.textContent = "\u21BB";
|
|
429
702
|
retry.addEventListener("click", () => this.#upload(item));
|
|
430
703
|
chip.appendChild(retry);
|
|
@@ -432,8 +705,8 @@ var AttachmentTray = class {
|
|
|
432
705
|
const remove = document.createElement("button");
|
|
433
706
|
remove.type = "button";
|
|
434
707
|
remove.className = "attachment-chip-remove";
|
|
435
|
-
remove.title =
|
|
436
|
-
remove.setAttribute("aria-label",
|
|
708
|
+
remove.title = this.#strings.remove;
|
|
709
|
+
remove.setAttribute("aria-label", this.#strings.removeAttachment);
|
|
437
710
|
remove.textContent = "\u2715";
|
|
438
711
|
remove.addEventListener("click", () => this.#remove(item));
|
|
439
712
|
chip.appendChild(remove);
|
|
@@ -463,26 +736,32 @@ function actionButton(modifier, label) {
|
|
|
463
736
|
const button = document.createElement("button");
|
|
464
737
|
button.type = "button";
|
|
465
738
|
button.className = `confirm-btn confirm-btn--${modifier}`;
|
|
739
|
+
button.setAttribute("part", `confirm-button confirm-${modifier}`);
|
|
466
740
|
button.textContent = label;
|
|
467
741
|
return button;
|
|
468
742
|
}
|
|
469
743
|
function requestConfirmation(host, request, options = {}) {
|
|
744
|
+
const strings = options.strings ?? DEFAULT_UI_STRINGS;
|
|
470
745
|
return new Promise((resolve) => {
|
|
471
746
|
const card = document.createElement("div");
|
|
472
747
|
card.className = "confirm";
|
|
748
|
+
card.setAttribute("part", "confirm");
|
|
473
749
|
card.setAttribute("data-tool-name", request.toolName);
|
|
474
750
|
card.setAttribute("role", "group");
|
|
475
|
-
card.setAttribute("aria-label",
|
|
751
|
+
card.setAttribute("aria-label", strings.confirmAction);
|
|
476
752
|
const body = document.createElement("div");
|
|
477
753
|
body.className = "confirm-body";
|
|
478
|
-
body.
|
|
754
|
+
body.setAttribute("part", "confirm-body");
|
|
755
|
+
body.textContent = request.message ?? strings.confirmRun.replace("{tool}", request.toolName);
|
|
479
756
|
const args = document.createElement("pre");
|
|
480
757
|
args.className = "confirm-args";
|
|
758
|
+
args.setAttribute("part", "confirm-args");
|
|
481
759
|
args.textContent = JSON.stringify(request.args, null, 2);
|
|
482
760
|
const actions = document.createElement("div");
|
|
483
761
|
actions.className = "confirm-actions";
|
|
484
|
-
|
|
485
|
-
const
|
|
762
|
+
actions.setAttribute("part", "confirm-actions");
|
|
763
|
+
const cancel = actionButton("cancel", strings.cancel);
|
|
764
|
+
const confirm = actionButton("confirm", strings.confirm);
|
|
486
765
|
let settled = false;
|
|
487
766
|
const close = (accepted) => {
|
|
488
767
|
if (settled) {
|
|
@@ -3318,6 +3597,71 @@ var STYLES = `
|
|
|
3318
3597
|
--ag-ui-radius: 0;
|
|
3319
3598
|
}
|
|
3320
3599
|
|
|
3600
|
+
/* Sidebar (CUST-3): a full-height docked panel that slides open/closed and
|
|
3601
|
+
collapses to a slim icon rail (not the floating launcher). Docked right by
|
|
3602
|
+
default; data-side="left" docks it left. Overlay by default \u2014 set
|
|
3603
|
+
--ag-ui-position: static (and place this element in your own layout) for a
|
|
3604
|
+
host-managed push instead. */
|
|
3605
|
+
:host([placement="sidebar"]) {
|
|
3606
|
+
--ag-ui-inset: 0 0 0 auto;
|
|
3607
|
+
--ag-ui-width: 420px;
|
|
3608
|
+
--ag-ui-height: 100vh;
|
|
3609
|
+
--ag-ui-max-height: 100vh;
|
|
3610
|
+
--ag-ui-radius: 0;
|
|
3611
|
+
--ag-ui-rail-width: 52px;
|
|
3612
|
+
transition: width 0.28s ease;
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
:host([placement="sidebar"][data-side="left"]) {
|
|
3616
|
+
--ag-ui-inset: 0 auto 0 0;
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
:host([placement="sidebar"]) .chat {
|
|
3620
|
+
transition: transform 0.28s ease;
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
/* Collapsed sidebar: shrink the host to the rail width, hide the panel, and
|
|
3624
|
+
reveal the rail. Higher specificity than the generic collapse rules, so it
|
|
3625
|
+
wins regardless of source order. */
|
|
3626
|
+
:host([placement="sidebar"][collapsed]) {
|
|
3627
|
+
width: var(--ag-ui-rail-width);
|
|
3628
|
+
height: 100vh;
|
|
3629
|
+
max-height: 100vh;
|
|
3630
|
+
bottom: 0;
|
|
3631
|
+
}
|
|
3632
|
+
|
|
3633
|
+
:host([placement="sidebar"][collapsed]) .chat {
|
|
3634
|
+
display: none;
|
|
3635
|
+
}
|
|
3636
|
+
|
|
3637
|
+
/* The rail is a sibling of the panel (so it survives the panel being hidden);
|
|
3638
|
+
shown only for a collapsed sidebar. */
|
|
3639
|
+
.rail {
|
|
3640
|
+
display: none;
|
|
3641
|
+
border: none;
|
|
3642
|
+
font: inherit;
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
:host([placement="sidebar"][collapsed]) .rail {
|
|
3646
|
+
display: flex;
|
|
3647
|
+
position: absolute;
|
|
3648
|
+
inset: 0;
|
|
3649
|
+
align-items: flex-start;
|
|
3650
|
+
justify-content: center;
|
|
3651
|
+
padding-top: 16px;
|
|
3652
|
+
border: 1px solid var(--ag-ui-border);
|
|
3653
|
+
background: var(--ag-ui-header-bg);
|
|
3654
|
+
color: var(--ag-ui-header-fg);
|
|
3655
|
+
cursor: pointer;
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3659
|
+
:host([placement="sidebar"]),
|
|
3660
|
+
:host([placement="sidebar"]) .chat {
|
|
3661
|
+
transition: none;
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3321
3665
|
/* Embedded: drop the floating chrome and the high z-index stacking context so
|
|
3322
3666
|
the widget lives in the host's own layout (fixes overlay/z-index clashes). */
|
|
3323
3667
|
:host([placement="embedded"]) {
|
|
@@ -3355,12 +3699,33 @@ var STYLES = `
|
|
|
3355
3699
|
}
|
|
3356
3700
|
|
|
3357
3701
|
.header-title {
|
|
3702
|
+
flex: 1;
|
|
3703
|
+
min-width: 0;
|
|
3358
3704
|
font-weight: 600;
|
|
3359
3705
|
overflow: hidden;
|
|
3360
3706
|
text-overflow: ellipsis;
|
|
3361
3707
|
white-space: nowrap;
|
|
3362
3708
|
}
|
|
3363
3709
|
|
|
3710
|
+
/* Header / launcher icon holder (CUST-2): a slot, with a data-icon-url <img>
|
|
3711
|
+
fallback, sized via --ag-ui-icon-size. */
|
|
3712
|
+
.icon-holder {
|
|
3713
|
+
display: inline-flex;
|
|
3714
|
+
align-items: center;
|
|
3715
|
+
justify-content: center;
|
|
3716
|
+
flex: none;
|
|
3717
|
+
width: var(--ag-ui-icon-size, 22px);
|
|
3718
|
+
height: var(--ag-ui-icon-size, 22px);
|
|
3719
|
+
line-height: 1;
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
.icon-img {
|
|
3723
|
+
width: 100%;
|
|
3724
|
+
height: 100%;
|
|
3725
|
+
object-fit: contain;
|
|
3726
|
+
border-radius: var(--ag-ui-icon-radius, 4px);
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3364
3729
|
.header-controls {
|
|
3365
3730
|
display: flex;
|
|
3366
3731
|
gap: 2px;
|
|
@@ -3416,6 +3781,18 @@ var STYLES = `
|
|
|
3416
3781
|
gap: var(--ag-ui-space);
|
|
3417
3782
|
}
|
|
3418
3783
|
|
|
3784
|
+
/* Empty-state region (CUST-1 slot): centred while it's the only thing in the
|
|
3785
|
+
list, hidden as soon as a message, card, or pending indicator renders. */
|
|
3786
|
+
.empty {
|
|
3787
|
+
margin: auto;
|
|
3788
|
+
text-align: center;
|
|
3789
|
+
color: var(--ag-ui-muted);
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
.empty[hidden] {
|
|
3793
|
+
display: none;
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3419
3796
|
.message {
|
|
3420
3797
|
max-width: 80%;
|
|
3421
3798
|
padding: var(--ag-ui-msg-pad);
|
|
@@ -4126,24 +4503,24 @@ var STYLES = `
|
|
|
4126
4503
|
`;
|
|
4127
4504
|
|
|
4128
4505
|
// src/ui/relative_time.ts
|
|
4129
|
-
function relativeTime(timestamp, now = Date.now()) {
|
|
4506
|
+
function relativeTime(timestamp, now = Date.now(), strings = DEFAULT_UI_STRINGS) {
|
|
4130
4507
|
const seconds = Math.round((now - timestamp) / 1e3);
|
|
4131
4508
|
if (seconds < 60) {
|
|
4132
|
-
return
|
|
4509
|
+
return strings.justNow;
|
|
4133
4510
|
}
|
|
4134
4511
|
const minutes = Math.round(seconds / 60);
|
|
4135
4512
|
if (minutes < 60) {
|
|
4136
|
-
return
|
|
4513
|
+
return strings.minutesAgo.replace("{n}", String(minutes));
|
|
4137
4514
|
}
|
|
4138
4515
|
const hours = Math.round(minutes / 60);
|
|
4139
4516
|
if (hours < 24) {
|
|
4140
|
-
return
|
|
4517
|
+
return strings.hoursAgo.replace("{n}", String(hours));
|
|
4141
4518
|
}
|
|
4142
4519
|
const days = Math.round(hours / 24);
|
|
4143
4520
|
if (days < 7) {
|
|
4144
|
-
return
|
|
4521
|
+
return strings.daysAgo.replace("{n}", String(days));
|
|
4145
4522
|
}
|
|
4146
|
-
return
|
|
4523
|
+
return strings.weeksAgo.replace("{n}", String(Math.round(days / 7)));
|
|
4147
4524
|
}
|
|
4148
4525
|
|
|
4149
4526
|
// src/ui/thread_drawer.ts
|
|
@@ -4151,39 +4528,59 @@ var ThreadDrawer = class {
|
|
|
4151
4528
|
/** The drawer root (backdrop + panel). Append to the chat shell; hidden until opened. */
|
|
4152
4529
|
element;
|
|
4153
4530
|
#callbacks;
|
|
4531
|
+
#panel;
|
|
4532
|
+
#heading;
|
|
4533
|
+
#newButton;
|
|
4154
4534
|
#list;
|
|
4535
|
+
#strings;
|
|
4155
4536
|
#threads = [];
|
|
4156
4537
|
#activeId = "";
|
|
4157
|
-
constructor(callbacks) {
|
|
4538
|
+
constructor(callbacks, strings = DEFAULT_UI_STRINGS) {
|
|
4158
4539
|
this.#callbacks = callbacks;
|
|
4540
|
+
this.#strings = strings;
|
|
4159
4541
|
this.element = document.createElement("div");
|
|
4160
4542
|
this.element.className = "drawer";
|
|
4543
|
+
this.element.setAttribute("part", "drawer");
|
|
4161
4544
|
this.element.hidden = true;
|
|
4162
4545
|
const backdrop = document.createElement("div");
|
|
4163
4546
|
backdrop.className = "drawer-backdrop";
|
|
4547
|
+
backdrop.setAttribute("part", "drawer-backdrop");
|
|
4164
4548
|
backdrop.addEventListener("click", () => this.close());
|
|
4165
|
-
|
|
4166
|
-
panel.className = "drawer-panel";
|
|
4167
|
-
panel.setAttribute("
|
|
4168
|
-
panel.setAttribute("
|
|
4549
|
+
this.#panel = document.createElement("div");
|
|
4550
|
+
this.#panel.className = "drawer-panel";
|
|
4551
|
+
this.#panel.setAttribute("part", "drawer-panel");
|
|
4552
|
+
this.#panel.setAttribute("role", "dialog");
|
|
4553
|
+
this.#panel.setAttribute("aria-label", strings.chatHistory);
|
|
4169
4554
|
const header = document.createElement("div");
|
|
4170
4555
|
header.className = "drawer-header";
|
|
4171
|
-
|
|
4172
|
-
heading
|
|
4173
|
-
heading.
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
newButton
|
|
4177
|
-
newButton.
|
|
4178
|
-
newButton.
|
|
4556
|
+
header.setAttribute("part", "drawer-header");
|
|
4557
|
+
this.#heading = document.createElement("span");
|
|
4558
|
+
this.#heading.className = "drawer-title";
|
|
4559
|
+
this.#heading.setAttribute("part", "drawer-title");
|
|
4560
|
+
this.#heading.textContent = strings.chats;
|
|
4561
|
+
this.#newButton = document.createElement("button");
|
|
4562
|
+
this.#newButton.type = "button";
|
|
4563
|
+
this.#newButton.className = "drawer-new";
|
|
4564
|
+
this.#newButton.setAttribute("part", "drawer-new");
|
|
4565
|
+
this.#newButton.textContent = strings.newChat;
|
|
4566
|
+
this.#newButton.addEventListener("click", () => {
|
|
4179
4567
|
this.close();
|
|
4180
4568
|
this.#callbacks.onNew();
|
|
4181
4569
|
});
|
|
4182
|
-
header.append(heading, newButton);
|
|
4570
|
+
header.append(this.#heading, this.#newButton);
|
|
4183
4571
|
this.#list = document.createElement("div");
|
|
4184
4572
|
this.#list.className = "drawer-list";
|
|
4185
|
-
|
|
4186
|
-
this.
|
|
4573
|
+
this.#list.setAttribute("part", "drawer-list");
|
|
4574
|
+
this.#panel.append(header, this.#list);
|
|
4575
|
+
this.element.append(backdrop, this.#panel);
|
|
4576
|
+
}
|
|
4577
|
+
/** Re-localize the drawer's chrome and rows (the host calls this on connect). */
|
|
4578
|
+
setStrings(strings) {
|
|
4579
|
+
this.#strings = strings;
|
|
4580
|
+
this.#panel.setAttribute("aria-label", strings.chatHistory);
|
|
4581
|
+
this.#heading.textContent = strings.chats;
|
|
4582
|
+
this.#newButton.textContent = strings.newChat;
|
|
4583
|
+
this.#renderList();
|
|
4187
4584
|
}
|
|
4188
4585
|
isOpen() {
|
|
4189
4586
|
return !this.element.hidden;
|
|
@@ -4208,7 +4605,8 @@ var ThreadDrawer = class {
|
|
|
4208
4605
|
if (this.#threads.length === 0) {
|
|
4209
4606
|
const empty = document.createElement("div");
|
|
4210
4607
|
empty.className = "drawer-empty";
|
|
4211
|
-
empty.
|
|
4608
|
+
empty.setAttribute("part", "drawer-empty");
|
|
4609
|
+
empty.textContent = this.#strings.noConversations;
|
|
4212
4610
|
this.#list.appendChild(empty);
|
|
4213
4611
|
return;
|
|
4214
4612
|
}
|
|
@@ -4219,18 +4617,20 @@ var ThreadDrawer = class {
|
|
|
4219
4617
|
#renderRow(meta) {
|
|
4220
4618
|
const row = document.createElement("div");
|
|
4221
4619
|
row.className = "drawer-row";
|
|
4620
|
+
row.setAttribute("part", "drawer-row");
|
|
4222
4621
|
if (meta.threadId === this.#activeId) {
|
|
4223
4622
|
row.classList.add("drawer-row--active");
|
|
4224
4623
|
}
|
|
4225
4624
|
const select = document.createElement("button");
|
|
4226
4625
|
select.type = "button";
|
|
4227
4626
|
select.className = "drawer-row-select";
|
|
4627
|
+
select.setAttribute("part", "drawer-row-select");
|
|
4228
4628
|
const title = document.createElement("span");
|
|
4229
4629
|
title.className = "drawer-row-title";
|
|
4230
4630
|
title.textContent = meta.title;
|
|
4231
4631
|
const time = document.createElement("span");
|
|
4232
4632
|
time.className = "drawer-row-time";
|
|
4233
|
-
time.textContent = relativeTime(meta.updatedAt);
|
|
4633
|
+
time.textContent = relativeTime(meta.updatedAt, void 0, this.#strings);
|
|
4234
4634
|
const preview = document.createElement("span");
|
|
4235
4635
|
preview.className = "drawer-row-preview";
|
|
4236
4636
|
preview.textContent = meta.preview;
|
|
@@ -4242,15 +4642,15 @@ var ThreadDrawer = class {
|
|
|
4242
4642
|
const rename = document.createElement("button");
|
|
4243
4643
|
rename.type = "button";
|
|
4244
4644
|
rename.className = "drawer-row-rename";
|
|
4245
|
-
rename.title =
|
|
4246
|
-
rename.setAttribute("aria-label",
|
|
4645
|
+
rename.title = this.#strings.rename;
|
|
4646
|
+
rename.setAttribute("aria-label", this.#strings.renameConversation);
|
|
4247
4647
|
rename.textContent = "\u270E";
|
|
4248
4648
|
rename.addEventListener("click", () => this.#startRename(row, meta));
|
|
4249
4649
|
const remove = document.createElement("button");
|
|
4250
4650
|
remove.type = "button";
|
|
4251
4651
|
remove.className = "drawer-row-delete";
|
|
4252
|
-
remove.title =
|
|
4253
|
-
remove.setAttribute("aria-label",
|
|
4652
|
+
remove.title = this.#strings.delete;
|
|
4653
|
+
remove.setAttribute("aria-label", this.#strings.deleteConversation);
|
|
4254
4654
|
remove.textContent = "\u{1F5D1}";
|
|
4255
4655
|
remove.addEventListener("click", () => this.#confirmDelete(row, meta));
|
|
4256
4656
|
const actions = document.createElement("div");
|
|
@@ -4287,16 +4687,16 @@ var ThreadDrawer = class {
|
|
|
4287
4687
|
confirm.className = "drawer-confirm";
|
|
4288
4688
|
const label = document.createElement("span");
|
|
4289
4689
|
label.className = "drawer-confirm-label";
|
|
4290
|
-
label.textContent =
|
|
4690
|
+
label.textContent = this.#strings.deletePrompt;
|
|
4291
4691
|
const yes = document.createElement("button");
|
|
4292
4692
|
yes.type = "button";
|
|
4293
4693
|
yes.className = "drawer-confirm-yes";
|
|
4294
|
-
yes.textContent =
|
|
4694
|
+
yes.textContent = this.#strings.delete;
|
|
4295
4695
|
yes.addEventListener("click", () => this.#callbacks.onDelete(meta.threadId));
|
|
4296
4696
|
const no = document.createElement("button");
|
|
4297
4697
|
no.type = "button";
|
|
4298
4698
|
no.className = "drawer-confirm-no";
|
|
4299
|
-
no.textContent =
|
|
4699
|
+
no.textContent = this.#strings.cancel;
|
|
4300
4700
|
no.addEventListener("click", () => this.#renderList());
|
|
4301
4701
|
confirm.append(label, yes, no);
|
|
4302
4702
|
row.replaceChildren(confirm);
|
|
@@ -4304,73 +4704,92 @@ var ThreadDrawer = class {
|
|
|
4304
4704
|
};
|
|
4305
4705
|
|
|
4306
4706
|
// src/ui/tool_call_card.ts
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4707
|
+
function statusLabels(strings) {
|
|
4708
|
+
return {
|
|
4709
|
+
[TOOL_CALL_STATUS.PENDING]: strings.toolRunning,
|
|
4710
|
+
[TOOL_CALL_STATUS.DONE]: strings.toolDone,
|
|
4711
|
+
[TOOL_CALL_STATUS.ERROR]: strings.toolError,
|
|
4712
|
+
[TOOL_CALL_STATUS.DECLINED]: strings.toolDeclined
|
|
4713
|
+
};
|
|
4714
|
+
}
|
|
4715
|
+
function resultLabels(strings) {
|
|
4716
|
+
return {
|
|
4717
|
+
[TOOL_CALL_STATUS.DONE]: strings.resultLabel,
|
|
4718
|
+
[TOOL_CALL_STATUS.ERROR]: strings.errorLabel,
|
|
4719
|
+
[TOOL_CALL_STATUS.DECLINED]: strings.declinedLabel
|
|
4720
|
+
};
|
|
4721
|
+
}
|
|
4318
4722
|
var ToolCallCard = class {
|
|
4319
4723
|
/** The card's root element; append this into the message list. */
|
|
4320
4724
|
element;
|
|
4321
4725
|
#status;
|
|
4322
4726
|
#mode;
|
|
4323
4727
|
#args;
|
|
4324
|
-
|
|
4728
|
+
#strings;
|
|
4729
|
+
#settled = false;
|
|
4730
|
+
constructor(name, args, mode = TOOL_DISPLAY.FULL, summary, strings = DEFAULT_UI_STRINGS) {
|
|
4325
4731
|
this.#mode = mode;
|
|
4326
4732
|
this.#args = args;
|
|
4733
|
+
this.#strings = strings;
|
|
4327
4734
|
this.element = document.createElement("div");
|
|
4328
4735
|
this.element.className = "tool-call";
|
|
4736
|
+
this.element.setAttribute("part", "tool-card");
|
|
4329
4737
|
this.element.setAttribute("data-tool-name", name);
|
|
4330
4738
|
this.element.setAttribute("data-status", TOOL_CALL_STATUS.PENDING);
|
|
4331
4739
|
this.element.setAttribute("data-display", mode);
|
|
4332
4740
|
const head = document.createElement("div");
|
|
4333
4741
|
head.className = "tool-call-head";
|
|
4742
|
+
head.setAttribute("part", "tool-card-head");
|
|
4334
4743
|
const label = document.createElement("span");
|
|
4335
4744
|
label.className = "tool-call-name";
|
|
4745
|
+
label.setAttribute("part", "tool-card-name");
|
|
4336
4746
|
label.textContent = `\u{1F527} ${summary ?? name}`;
|
|
4337
4747
|
this.#status = document.createElement("span");
|
|
4338
4748
|
this.#status.className = "tool-call-status";
|
|
4339
|
-
this.#status.
|
|
4749
|
+
this.#status.setAttribute("part", "tool-card-status");
|
|
4750
|
+
this.#status.textContent = statusLabels(strings)[TOOL_CALL_STATUS.PENDING];
|
|
4340
4751
|
head.append(label, this.#status);
|
|
4341
4752
|
this.element.append(head);
|
|
4342
4753
|
if (mode === TOOL_DISPLAY.FULL) {
|
|
4343
4754
|
const argsEl = document.createElement("pre");
|
|
4344
4755
|
argsEl.className = "tool-call-args";
|
|
4756
|
+
argsEl.setAttribute("part", "tool-card-args");
|
|
4345
4757
|
argsEl.textContent = JSON.stringify(args, null, 2);
|
|
4346
4758
|
this.element.append(argsEl);
|
|
4347
4759
|
}
|
|
4348
4760
|
}
|
|
4761
|
+
/** Whether {@link settle} has already run (so a terminal sweep can skip it). */
|
|
4762
|
+
get settled() {
|
|
4763
|
+
return this.#settled;
|
|
4764
|
+
}
|
|
4349
4765
|
/**
|
|
4350
4766
|
* Flip the status pill to ``status`` and, unless in `minimal` mode, append a
|
|
4351
4767
|
* collapsed body behind a click-to-expand toggle: the result alone (`full`),
|
|
4352
4768
|
* or the args + result together (`compact`).
|
|
4353
4769
|
*/
|
|
4354
4770
|
settle(status, text2) {
|
|
4771
|
+
this.#settled = true;
|
|
4355
4772
|
this.element.setAttribute("data-status", status);
|
|
4356
|
-
this.#status.textContent =
|
|
4773
|
+
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
4357
4774
|
if (this.#mode === TOOL_DISPLAY.MINIMAL) {
|
|
4358
4775
|
return;
|
|
4359
4776
|
}
|
|
4360
4777
|
const toggle = document.createElement("button");
|
|
4361
4778
|
toggle.type = "button";
|
|
4362
4779
|
toggle.className = "tool-call-toggle";
|
|
4780
|
+
toggle.setAttribute("part", "tool-card-toggle");
|
|
4363
4781
|
toggle.setAttribute("aria-expanded", "false");
|
|
4364
4782
|
const output = document.createElement("pre");
|
|
4365
4783
|
output.className = "tool-call-result";
|
|
4784
|
+
output.setAttribute("part", "tool-card-result");
|
|
4366
4785
|
output.hidden = true;
|
|
4367
4786
|
if (this.#mode === TOOL_DISPLAY.COMPACT) {
|
|
4368
|
-
toggle.textContent =
|
|
4787
|
+
toggle.textContent = this.#strings.details;
|
|
4369
4788
|
output.textContent = `args: ${JSON.stringify(this.#args)}
|
|
4370
4789
|
|
|
4371
4790
|
${text2}`;
|
|
4372
4791
|
} else {
|
|
4373
|
-
toggle.textContent =
|
|
4792
|
+
toggle.textContent = resultLabels(this.#strings)[status];
|
|
4374
4793
|
output.textContent = text2;
|
|
4375
4794
|
}
|
|
4376
4795
|
toggle.addEventListener("click", () => {
|
|
@@ -4384,6 +4803,12 @@ ${text2}`;
|
|
|
4384
4803
|
|
|
4385
4804
|
// src/core/agui_client.ts
|
|
4386
4805
|
import { randomUUID as randomUUID2 } from "@ag-ui/client";
|
|
4806
|
+
var ConnectionLostError = class extends Error {
|
|
4807
|
+
constructor(message) {
|
|
4808
|
+
super(message);
|
|
4809
|
+
this.name = "ConnectionLostError";
|
|
4810
|
+
}
|
|
4811
|
+
};
|
|
4387
4812
|
var AgUiClient = class {
|
|
4388
4813
|
#agent;
|
|
4389
4814
|
#handlers;
|
|
@@ -4391,6 +4816,7 @@ var AgUiClient = class {
|
|
|
4391
4816
|
#getContext;
|
|
4392
4817
|
#executeTool;
|
|
4393
4818
|
#onPersist;
|
|
4819
|
+
#connectionLostMessage;
|
|
4394
4820
|
// Set by cancel(); reset at the top of each #run(). Checked by the loop so
|
|
4395
4821
|
// a cancel between frontend-tool rounds doesn't start another round.
|
|
4396
4822
|
#cancelled = false;
|
|
@@ -4402,6 +4828,7 @@ var AgUiClient = class {
|
|
|
4402
4828
|
this.#executeTool = config.executeTool ?? null;
|
|
4403
4829
|
this.#onPersist = config.onPersist ?? (() => {
|
|
4404
4830
|
});
|
|
4831
|
+
this.#connectionLostMessage = config.connectionLostMessage ?? "Connection lost";
|
|
4405
4832
|
}
|
|
4406
4833
|
/** Whether a run is currently in flight. */
|
|
4407
4834
|
get running() {
|
|
@@ -4483,14 +4910,18 @@ var AgUiClient = class {
|
|
|
4483
4910
|
return;
|
|
4484
4911
|
}
|
|
4485
4912
|
const pending = [];
|
|
4913
|
+
const runState = { terminal: false };
|
|
4486
4914
|
await this.#agent.runAgent(
|
|
4487
4915
|
{ tools: this.#getTools(), context: this.#getContext() },
|
|
4488
|
-
this.#buildSubscriber(pending)
|
|
4916
|
+
this.#buildSubscriber(pending, runState)
|
|
4489
4917
|
);
|
|
4490
4918
|
this.#onPersist(this.#agent.messages);
|
|
4491
4919
|
if (this.#cancelled) {
|
|
4492
4920
|
return;
|
|
4493
4921
|
}
|
|
4922
|
+
if (!runState.terminal) {
|
|
4923
|
+
throw new ConnectionLostError(this.#connectionLostMessage);
|
|
4924
|
+
}
|
|
4494
4925
|
if (this.#executeTool === null || pending.length === 0) {
|
|
4495
4926
|
return;
|
|
4496
4927
|
}
|
|
@@ -4517,7 +4948,7 @@ var AgUiClient = class {
|
|
|
4517
4948
|
}
|
|
4518
4949
|
}
|
|
4519
4950
|
}
|
|
4520
|
-
#buildSubscriber(pending) {
|
|
4951
|
+
#buildSubscriber(pending, runState) {
|
|
4521
4952
|
const h = this.#handlers;
|
|
4522
4953
|
return {
|
|
4523
4954
|
onRunInitialized() {
|
|
@@ -4542,9 +4973,11 @@ var AgUiClient = class {
|
|
|
4542
4973
|
h.onToolResult(event.toolCallId, event.content);
|
|
4543
4974
|
},
|
|
4544
4975
|
onRunErrorEvent({ event }) {
|
|
4976
|
+
runState.terminal = true;
|
|
4545
4977
|
h.onError(event.message);
|
|
4546
4978
|
},
|
|
4547
4979
|
onRunFinalized() {
|
|
4980
|
+
runState.terminal = true;
|
|
4548
4981
|
h.onRunEnd();
|
|
4549
4982
|
}
|
|
4550
4983
|
};
|
|
@@ -4982,12 +5415,28 @@ var AgUiChat = class extends HTMLElement {
|
|
|
4982
5415
|
* schema; this map is the seam for everything else.
|
|
4983
5416
|
*/
|
|
4984
5417
|
toolSummaries = {};
|
|
5418
|
+
/**
|
|
5419
|
+
* Localizable UI strings — a partial override merged over the English
|
|
5420
|
+
* {@link DEFAULT_UI_STRINGS}. Resolved once on connect (so set it before the
|
|
5421
|
+
* element is appended); the `data-strings` JSON attribute is the markup
|
|
5422
|
+
* equivalent, and this property wins key-by-key over it.
|
|
5423
|
+
*/
|
|
5424
|
+
strings = {};
|
|
5425
|
+
/**
|
|
5426
|
+
* Resolve a `scroll_to` / `drag_and_drop` target string to a host-page
|
|
5427
|
+
* element (or `null`). Defaults to a CSS-selector lookup; override to map
|
|
5428
|
+
* page-map element ids. The page-action tools are opt-in via the
|
|
5429
|
+
* `data-page-actions` attribute (`"scroll"` / `"drag"`).
|
|
5430
|
+
*/
|
|
5431
|
+
resolvePageTarget = (target) => document.querySelector(target);
|
|
4985
5432
|
/**
|
|
4986
5433
|
* Card labels fetched from a server tool catalog (`data-tools-url`), keyed by
|
|
4987
5434
|
* tool name. The base layer behind {@link toolSummaries}: an explicit entry in
|
|
4988
5435
|
* `toolSummaries` wins, this fills the rest. Populated once on connect.
|
|
4989
5436
|
*/
|
|
4990
5437
|
#toolCatalog = {};
|
|
5438
|
+
/** The resolved string table (defaults ← `data-strings` ← `strings`). */
|
|
5439
|
+
#strings = DEFAULT_UI_STRINGS;
|
|
4991
5440
|
#toolRegistry = new ClientToolRegistry();
|
|
4992
5441
|
/** Tool-call cards awaiting execution, keyed by call id. */
|
|
4993
5442
|
#toolCards = /* @__PURE__ */ new Map();
|
|
@@ -5010,6 +5459,10 @@ var AgUiChat = class extends HTMLElement {
|
|
|
5010
5459
|
#attachButton;
|
|
5011
5460
|
#fileInput;
|
|
5012
5461
|
#attachSlot;
|
|
5462
|
+
/** The collapsed-sidebar rail (an expand affordance; shown only for `placement="sidebar"`). */
|
|
5463
|
+
#rail;
|
|
5464
|
+
/** Empty-state region at the top of the message list; hidden once anything renders. */
|
|
5465
|
+
#emptyWrap;
|
|
5013
5466
|
/** Upload tray; created on connect only when `data-attachments-url` is set. */
|
|
5014
5467
|
#attachTray = null;
|
|
5015
5468
|
/** Refs attached to the message currently being sent (the context manifest). */
|
|
@@ -5046,6 +5499,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
5046
5499
|
this.#attachButton = document.createElement("button");
|
|
5047
5500
|
this.#fileInput = document.createElement("input");
|
|
5048
5501
|
this.#attachSlot = document.createElement("div");
|
|
5502
|
+
this.#rail = document.createElement("button");
|
|
5503
|
+
this.#emptyWrap = document.createElement("div");
|
|
5049
5504
|
this.#skillsMenu = new SkillsMenu((skill) => this.#applySkill(skill));
|
|
5050
5505
|
this.#drawer = new ThreadDrawer({
|
|
5051
5506
|
onSelect: (threadId) => {
|
|
@@ -5069,7 +5524,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
5069
5524
|
return ["title-text"];
|
|
5070
5525
|
}
|
|
5071
5526
|
attributeChangedCallback(_name, _previous, value) {
|
|
5072
|
-
this.#title.textContent = value ??
|
|
5527
|
+
this.#title.textContent = value ?? this.#strings.title;
|
|
5073
5528
|
}
|
|
5074
5529
|
/** Declare a frontend tool the agent may call. */
|
|
5075
5530
|
registerTool(tool) {
|
|
@@ -5115,9 +5570,25 @@ var AgUiChat = class extends HTMLElement {
|
|
|
5115
5570
|
}
|
|
5116
5571
|
];
|
|
5117
5572
|
}
|
|
5118
|
-
/**
|
|
5573
|
+
/**
|
|
5574
|
+
* Opt-in page-action tools (`scroll_to` / `drag_and_drop`), enabled per token
|
|
5575
|
+
* via the `data-page-actions` attribute (e.g. `"scroll,drag"`). Targets resolve
|
|
5576
|
+
* through {@link resolvePageTarget} so a host controls the agent's interaction
|
|
5577
|
+
* surface; absent attribute ⇒ no tools registered.
|
|
5578
|
+
*/
|
|
5579
|
+
#pageActionTools() {
|
|
5580
|
+
const attr = this.getAttribute("data-page-actions");
|
|
5581
|
+
if (attr === null) {
|
|
5582
|
+
return [];
|
|
5583
|
+
}
|
|
5584
|
+
const enabled = new Set(
|
|
5585
|
+
attr.split(",").map((token) => token.trim()).filter((token) => token !== "")
|
|
5586
|
+
);
|
|
5587
|
+
return createPageActionTools(enabled, (target) => this.resolvePageTarget(target));
|
|
5588
|
+
}
|
|
5589
|
+
/** All built-in (route + page + page-action) frontend tools. */
|
|
5119
5590
|
#builtinTools() {
|
|
5120
|
-
return [...this.#routeTools(), ...this.#pageTools()];
|
|
5591
|
+
return [...this.#routeTools(), ...this.#pageTools(), ...this.#pageActionTools()];
|
|
5121
5592
|
}
|
|
5122
5593
|
/** Resolve a tool by name: built-in tools first, then the registry. */
|
|
5123
5594
|
#resolveTool(name) {
|
|
@@ -5152,10 +5623,13 @@ var AgUiChat = class extends HTMLElement {
|
|
|
5152
5623
|
this.setAttribute("data-tool-display", value);
|
|
5153
5624
|
}
|
|
5154
5625
|
connectedCallback() {
|
|
5626
|
+
this.#strings = mergeUiStrings({ ...this.#readStringOverrides(), ...this.strings });
|
|
5155
5627
|
this.#render();
|
|
5628
|
+
this.#drawer.setStrings(this.#strings);
|
|
5156
5629
|
if (sessionStorage.getItem(COLLAPSED_KEY) === "1") {
|
|
5157
5630
|
this.setAttribute("collapsed", "");
|
|
5158
5631
|
}
|
|
5632
|
+
this.#syncRail();
|
|
5159
5633
|
this.#initSkills();
|
|
5160
5634
|
void this.#fetchToolCatalog();
|
|
5161
5635
|
this.#wireThreadStore();
|
|
@@ -5163,6 +5637,21 @@ var AgUiChat = class extends HTMLElement {
|
|
|
5163
5637
|
this.#threadId = this.conversationStore.threadId();
|
|
5164
5638
|
void this.#rehydrate();
|
|
5165
5639
|
}
|
|
5640
|
+
/** Parse the inline `data-strings` JSON overrides (empty when absent/malformed). */
|
|
5641
|
+
#readStringOverrides() {
|
|
5642
|
+
const raw = this.getAttribute("data-strings");
|
|
5643
|
+
if (raw === null) {
|
|
5644
|
+
return {};
|
|
5645
|
+
}
|
|
5646
|
+
try {
|
|
5647
|
+
const parsed = JSON.parse(raw);
|
|
5648
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
5649
|
+
return parsed;
|
|
5650
|
+
}
|
|
5651
|
+
} catch {
|
|
5652
|
+
}
|
|
5653
|
+
return {};
|
|
5654
|
+
}
|
|
5166
5655
|
/**
|
|
5167
5656
|
* Enable the composer's file-upload tray when uploads are possible — either a
|
|
5168
5657
|
* custom {@link uploadHandler} is set or `data-attachments-url` provides the
|
|
@@ -5180,7 +5669,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
5180
5669
|
this.#attachTray = new AttachmentTray({
|
|
5181
5670
|
upload,
|
|
5182
5671
|
maxBytes: this.#attachmentMaxBytes(),
|
|
5183
|
-
accept
|
|
5672
|
+
accept,
|
|
5673
|
+
strings: this.#strings
|
|
5184
5674
|
});
|
|
5185
5675
|
this.#attachSlot.appendChild(this.#attachTray.element);
|
|
5186
5676
|
this.#fileInput.accept = accept;
|
|
@@ -5330,7 +5820,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5330
5820
|
#applySkill(skill) {
|
|
5331
5821
|
const { text: text2, missing } = fillTemplate(skill.prompt, this.skillContext());
|
|
5332
5822
|
if (missing.length > 0) {
|
|
5333
|
-
this.#skillHint.textContent =
|
|
5823
|
+
this.#skillHint.textContent = this.#strings.skillNeeds.replace("{title}", skill.title).replace("{fields}", missing.join(", "));
|
|
5334
5824
|
this.#skillHint.hidden = false;
|
|
5335
5825
|
return;
|
|
5336
5826
|
}
|
|
@@ -5363,6 +5853,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5363
5853
|
this.removeAttribute("collapsed");
|
|
5364
5854
|
}
|
|
5365
5855
|
sessionStorage.setItem(COLLAPSED_KEY, collapsed ? "1" : "0");
|
|
5856
|
+
this.#syncRail();
|
|
5366
5857
|
this.dispatchEvent(
|
|
5367
5858
|
new CustomEvent(TOGGLE_EVENT, {
|
|
5368
5859
|
detail: { collapsed },
|
|
@@ -5396,7 +5887,8 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5396
5887
|
this.#initialMessages = [];
|
|
5397
5888
|
this.#runAttachments = [];
|
|
5398
5889
|
this.#attachTray?.clear();
|
|
5399
|
-
this.#messages.replaceChildren();
|
|
5890
|
+
this.#messages.replaceChildren(this.#emptyWrap);
|
|
5891
|
+
this.#updateEmptyState();
|
|
5400
5892
|
}
|
|
5401
5893
|
/** Switch the active conversation to an existing thread and replay it. */
|
|
5402
5894
|
async #switchThread(threadId) {
|
|
@@ -5524,12 +6016,14 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5524
6016
|
appendMessage(role, content) {
|
|
5525
6017
|
const bubble = document.createElement("div");
|
|
5526
6018
|
bubble.className = `message message--${role}`;
|
|
6019
|
+
bubble.setAttribute("part", `message message-${role}`);
|
|
5527
6020
|
if (role === MESSAGE_ROLE.ASSISTANT) {
|
|
5528
6021
|
bubble.innerHTML = renderMarkdown(content, { allowImages: this.allowImages });
|
|
5529
6022
|
} else {
|
|
5530
6023
|
bubble.textContent = content;
|
|
5531
6024
|
}
|
|
5532
6025
|
this.#messages.appendChild(bubble);
|
|
6026
|
+
this.#updateEmptyState();
|
|
5533
6027
|
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
5534
6028
|
return bubble;
|
|
5535
6029
|
}
|
|
@@ -5537,55 +6031,59 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5537
6031
|
const style = document.createElement("style");
|
|
5538
6032
|
style.textContent = STYLES;
|
|
5539
6033
|
this.#chat.className = "chat";
|
|
6034
|
+
this.#chat.setAttribute("part", "panel");
|
|
5540
6035
|
const header = document.createElement("div");
|
|
5541
6036
|
header.className = "header";
|
|
6037
|
+
header.setAttribute("part", "header");
|
|
5542
6038
|
const title = this.#title;
|
|
5543
6039
|
title.className = "header-title";
|
|
5544
|
-
title.
|
|
6040
|
+
title.setAttribute("part", "title");
|
|
6041
|
+
title.textContent = this.getAttribute("title-text") ?? this.#strings.title;
|
|
6042
|
+
if (this.querySelector('[slot="icon"]') !== null || this.getAttribute("data-icon-url") !== null) {
|
|
6043
|
+
header.append(this.#iconElement("icon", "icon", null));
|
|
6044
|
+
}
|
|
6045
|
+
const headerActions = document.createElement("slot");
|
|
6046
|
+
headerActions.name = "header-actions";
|
|
5545
6047
|
const controls = document.createElement("div");
|
|
5546
6048
|
controls.className = "header-controls";
|
|
5547
|
-
|
|
5548
|
-
history
|
|
5549
|
-
history.className = "header-btn header-btn--history";
|
|
5550
|
-
history.title = "Chat history";
|
|
5551
|
-
history.setAttribute("aria-label", "Chat history");
|
|
5552
|
-
history.textContent = "\u2630";
|
|
6049
|
+
controls.setAttribute("part", "header-controls");
|
|
6050
|
+
const history = this.#headerButton("history", this.#strings.chatHistory, "\u2630");
|
|
5553
6051
|
history.addEventListener("click", () => {
|
|
5554
6052
|
void this.#refreshDrawer();
|
|
5555
6053
|
this.#drawer.open();
|
|
5556
6054
|
});
|
|
5557
|
-
const newChat =
|
|
5558
|
-
newChat.type = "button";
|
|
5559
|
-
newChat.className = "header-btn header-btn--new";
|
|
5560
|
-
newChat.title = "New chat";
|
|
5561
|
-
newChat.setAttribute("aria-label", "New chat");
|
|
5562
|
-
newChat.textContent = "\u271A";
|
|
6055
|
+
const newChat = this.#headerButton("new", this.#strings.newChat, "\u271A");
|
|
5563
6056
|
newChat.addEventListener("click", () => this.newChat());
|
|
5564
|
-
const collapse =
|
|
5565
|
-
collapse.type = "button";
|
|
5566
|
-
collapse.className = "header-btn header-btn--collapse";
|
|
5567
|
-
collapse.title = "Collapse";
|
|
5568
|
-
collapse.setAttribute("aria-label", "Collapse");
|
|
5569
|
-
collapse.textContent = "\u2014";
|
|
6057
|
+
const collapse = this.#headerButton("collapse", this.#strings.collapse, "\u2014");
|
|
5570
6058
|
collapse.addEventListener("click", () => this.toggleCollapsed());
|
|
5571
6059
|
controls.append(history, newChat, collapse);
|
|
5572
|
-
header.append(title, controls);
|
|
6060
|
+
header.append(title, headerActions, controls);
|
|
5573
6061
|
this.#messages.className = "messages";
|
|
6062
|
+
this.#messages.setAttribute("part", "messages");
|
|
5574
6063
|
this.#messages.setAttribute("role", "log");
|
|
5575
6064
|
this.#messages.setAttribute("aria-live", "polite");
|
|
5576
|
-
this.#messages.setAttribute("aria-label",
|
|
6065
|
+
this.#messages.setAttribute("aria-label", this.#strings.conversation);
|
|
6066
|
+
this.#emptyWrap.className = "empty";
|
|
6067
|
+
this.#emptyWrap.setAttribute("part", "empty");
|
|
6068
|
+
const emptySlot = document.createElement("slot");
|
|
6069
|
+
emptySlot.name = "empty";
|
|
6070
|
+
this.#emptyWrap.append(emptySlot);
|
|
6071
|
+
this.#messages.append(this.#emptyWrap);
|
|
5577
6072
|
const inputRow = document.createElement("div");
|
|
5578
6073
|
inputRow.className = "input-row";
|
|
6074
|
+
inputRow.setAttribute("part", "composer");
|
|
5579
6075
|
this.#input.className = "input";
|
|
5580
|
-
this.#input.setAttribute("
|
|
6076
|
+
this.#input.setAttribute("part", "input");
|
|
6077
|
+
this.#input.setAttribute("aria-label", this.#strings.message);
|
|
5581
6078
|
this.#input.rows = 2;
|
|
5582
|
-
this.#input.placeholder =
|
|
6079
|
+
this.#input.placeholder = this.#strings.inputPlaceholder;
|
|
5583
6080
|
this.#input.addEventListener("keydown", (event) => this.#onKeydown(event));
|
|
5584
6081
|
this.#input.addEventListener("input", () => this.#onInput());
|
|
5585
6082
|
this.#send.className = "send";
|
|
5586
6083
|
this.#send.type = "button";
|
|
5587
|
-
this.#send.
|
|
5588
|
-
this.#send.
|
|
6084
|
+
this.#send.setAttribute("part", "send");
|
|
6085
|
+
this.#send.textContent = this.#strings.send;
|
|
6086
|
+
this.#send.setAttribute("aria-label", this.#strings.send);
|
|
5589
6087
|
this.#send.dataset["state"] = "idle";
|
|
5590
6088
|
this.#send.addEventListener("click", () => {
|
|
5591
6089
|
if (this.#running) {
|
|
@@ -5598,9 +6096,10 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5598
6096
|
this.#skillHint.hidden = true;
|
|
5599
6097
|
this.#attachButton.className = "attach-btn";
|
|
5600
6098
|
this.#attachButton.type = "button";
|
|
6099
|
+
this.#attachButton.setAttribute("part", "attach-button");
|
|
5601
6100
|
this.#attachButton.textContent = "\u{1F4CE}";
|
|
5602
|
-
this.#attachButton.title =
|
|
5603
|
-
this.#attachButton.setAttribute("aria-label",
|
|
6101
|
+
this.#attachButton.title = this.#strings.attachFiles;
|
|
6102
|
+
this.#attachButton.setAttribute("aria-label", this.#strings.attachFiles);
|
|
5604
6103
|
this.#attachButton.hidden = true;
|
|
5605
6104
|
this.#attachButton.addEventListener("click", () => this.#fileInput.click());
|
|
5606
6105
|
this.#fileInput.className = "attach-input";
|
|
@@ -5609,6 +6108,8 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5609
6108
|
this.#fileInput.hidden = true;
|
|
5610
6109
|
this.#fileInput.addEventListener("change", () => this.#onFilesPicked());
|
|
5611
6110
|
this.#attachSlot.className = "attachment-slot";
|
|
6111
|
+
const footer = document.createElement("slot");
|
|
6112
|
+
footer.name = "footer";
|
|
5612
6113
|
inputRow.append(this.#attachButton, this.#input, this.#send, this.#fileInput);
|
|
5613
6114
|
this.#chat.append(
|
|
5614
6115
|
header,
|
|
@@ -5618,9 +6119,58 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5618
6119
|
this.#skillHint,
|
|
5619
6120
|
this.#attachSlot,
|
|
5620
6121
|
inputRow,
|
|
6122
|
+
footer,
|
|
5621
6123
|
this.#drawer.element
|
|
5622
6124
|
);
|
|
5623
|
-
this.#
|
|
6125
|
+
this.#rail.className = "rail";
|
|
6126
|
+
this.#rail.type = "button";
|
|
6127
|
+
this.#rail.setAttribute("part", "launcher");
|
|
6128
|
+
this.#rail.setAttribute("aria-label", this.#strings.expand);
|
|
6129
|
+
this.#rail.append(this.#iconElement("launcher", "launcher-icon", "\u{1F4AC}"));
|
|
6130
|
+
this.#rail.addEventListener("click", () => this.setCollapsed(false));
|
|
6131
|
+
this.#root.append(style, this.#chat, this.#rail);
|
|
6132
|
+
}
|
|
6133
|
+
/** Build a header control button (icon glyph + localized title/aria). */
|
|
6134
|
+
#headerButton(modifier, label, glyph) {
|
|
6135
|
+
const button = document.createElement("button");
|
|
6136
|
+
button.type = "button";
|
|
6137
|
+
button.className = `header-btn header-btn--${modifier}`;
|
|
6138
|
+
button.setAttribute("part", `header-button ${modifier}-button`);
|
|
6139
|
+
button.title = label;
|
|
6140
|
+
button.setAttribute("aria-label", label);
|
|
6141
|
+
button.textContent = glyph;
|
|
6142
|
+
return button;
|
|
6143
|
+
}
|
|
6144
|
+
/**
|
|
6145
|
+
* An icon holder wrapping a `<slot>` so a host can project custom markup; with
|
|
6146
|
+
* a `data-icon-url` `<img>` as the slot's fallback, or a glyph when given.
|
|
6147
|
+
*/
|
|
6148
|
+
#iconElement(slotName, part, fallbackGlyph) {
|
|
6149
|
+
const holder = document.createElement("span");
|
|
6150
|
+
holder.className = "icon-holder";
|
|
6151
|
+
holder.setAttribute("part", part);
|
|
6152
|
+
const slot = document.createElement("slot");
|
|
6153
|
+
slot.name = slotName;
|
|
6154
|
+
const iconUrl = this.getAttribute("data-icon-url");
|
|
6155
|
+
if (iconUrl !== null) {
|
|
6156
|
+
const img = document.createElement("img");
|
|
6157
|
+
img.className = "icon-img";
|
|
6158
|
+
img.src = iconUrl;
|
|
6159
|
+
img.alt = "";
|
|
6160
|
+
slot.append(img);
|
|
6161
|
+
} else if (fallbackGlyph !== null) {
|
|
6162
|
+
slot.append(document.createTextNode(fallbackGlyph));
|
|
6163
|
+
}
|
|
6164
|
+
holder.append(slot);
|
|
6165
|
+
return holder;
|
|
6166
|
+
}
|
|
6167
|
+
/** Reflect the collapsed state on the rail's `aria-expanded`. */
|
|
6168
|
+
#syncRail() {
|
|
6169
|
+
this.#rail.setAttribute("aria-expanded", String(!this.collapsed));
|
|
6170
|
+
}
|
|
6171
|
+
/** Hide the empty-state region once the message list holds anything else. */
|
|
6172
|
+
#updateEmptyState() {
|
|
6173
|
+
this.#emptyWrap.hidden = this.#messages.childElementCount > 1;
|
|
5624
6174
|
}
|
|
5625
6175
|
/** Forward input changes to the skills palette and clear any stale hint. */
|
|
5626
6176
|
#onInput() {
|
|
@@ -5655,7 +6205,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5655
6205
|
/** Swap the composer button between Send (idle) and Stop (running). */
|
|
5656
6206
|
#setRunning(running) {
|
|
5657
6207
|
this.#running = running;
|
|
5658
|
-
const label = running ?
|
|
6208
|
+
const label = running ? this.#strings.stop : this.#strings.send;
|
|
5659
6209
|
this.#send.textContent = label;
|
|
5660
6210
|
this.#send.setAttribute("aria-label", label);
|
|
5661
6211
|
this.#send.dataset["state"] = running ? "running" : "idle";
|
|
@@ -5706,7 +6256,8 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5706
6256
|
getTools: () => this.getTools(),
|
|
5707
6257
|
getContext: () => this.getContext(),
|
|
5708
6258
|
executeTool: (call) => this.#executeTool(call),
|
|
5709
|
-
onPersist: (messages) => this.conversationStore.saveMessages(this.#threadId, messages)
|
|
6259
|
+
onPersist: (messages) => this.conversationStore.saveMessages(this.#threadId, messages),
|
|
6260
|
+
connectionLostMessage: this.#strings.connectionLost
|
|
5710
6261
|
});
|
|
5711
6262
|
}
|
|
5712
6263
|
return this.#client;
|
|
@@ -5727,7 +6278,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5727
6278
|
const tool = this.#resolveTool(call.name);
|
|
5728
6279
|
if (tool === null) {
|
|
5729
6280
|
if (!this.#serverSettled.has(call.id)) {
|
|
5730
|
-
card.settle(TOOL_CALL_STATUS.DONE,
|
|
6281
|
+
card.settle(TOOL_CALL_STATUS.DONE, this.#strings.noResult);
|
|
5731
6282
|
}
|
|
5732
6283
|
return null;
|
|
5733
6284
|
}
|
|
@@ -5739,13 +6290,15 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5739
6290
|
}
|
|
5740
6291
|
this.#confirmAbort = new AbortController();
|
|
5741
6292
|
const decision = requestConfirmation(this.#messages, request, {
|
|
5742
|
-
signal: this.#confirmAbort.signal
|
|
6293
|
+
signal: this.#confirmAbort.signal,
|
|
6294
|
+
strings: this.#strings
|
|
5743
6295
|
});
|
|
6296
|
+
this.#updateEmptyState();
|
|
5744
6297
|
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
5745
6298
|
const accepted = await decision;
|
|
5746
6299
|
this.#confirmAbort = null;
|
|
5747
6300
|
if (!accepted) {
|
|
5748
|
-
const message =
|
|
6301
|
+
const message = this.#strings.declinedAction;
|
|
5749
6302
|
card.settle(TOOL_CALL_STATUS.DECLINED, message);
|
|
5750
6303
|
this.#showPending();
|
|
5751
6304
|
return { content: message };
|
|
@@ -5758,7 +6311,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5758
6311
|
try {
|
|
5759
6312
|
const result = await tool.handler(call.args);
|
|
5760
6313
|
if (navigates) {
|
|
5761
|
-
card.settle(TOOL_CALL_STATUS.DONE,
|
|
6314
|
+
card.settle(TOOL_CALL_STATUS.DONE, this.#strings.navigating);
|
|
5762
6315
|
return { content: "", halt: true };
|
|
5763
6316
|
}
|
|
5764
6317
|
const content = JSON.stringify(result ?? null);
|
|
@@ -5824,6 +6377,11 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5824
6377
|
this.#setRunning(false);
|
|
5825
6378
|
this.#streamingBubble = null;
|
|
5826
6379
|
this.#runAttachments = [];
|
|
6380
|
+
for (const card of this.#toolCards.values()) {
|
|
6381
|
+
if (!card.settled) {
|
|
6382
|
+
card.settle(TOOL_CALL_STATUS.DONE, this.#strings.noResult);
|
|
6383
|
+
}
|
|
6384
|
+
}
|
|
5827
6385
|
}
|
|
5828
6386
|
};
|
|
5829
6387
|
}
|
|
@@ -5831,9 +6389,11 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5831
6389
|
#appendStoppedNote() {
|
|
5832
6390
|
const note = document.createElement("div");
|
|
5833
6391
|
note.className = "stopped-note";
|
|
6392
|
+
note.setAttribute("part", "stopped");
|
|
5834
6393
|
note.setAttribute("role", "status");
|
|
5835
|
-
note.textContent =
|
|
6394
|
+
note.textContent = this.#strings.stopped;
|
|
5836
6395
|
this.#messages.appendChild(note);
|
|
6396
|
+
this.#updateEmptyState();
|
|
5837
6397
|
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
5838
6398
|
}
|
|
5839
6399
|
/**
|
|
@@ -5847,8 +6407,9 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5847
6407
|
}
|
|
5848
6408
|
const pending = document.createElement("div");
|
|
5849
6409
|
pending.className = "pending";
|
|
6410
|
+
pending.setAttribute("part", "pending");
|
|
5850
6411
|
pending.setAttribute("role", "status");
|
|
5851
|
-
pending.setAttribute("aria-label",
|
|
6412
|
+
pending.setAttribute("aria-label", this.#strings.thinking);
|
|
5852
6413
|
for (let i = 0; i < 3; i += 1) {
|
|
5853
6414
|
const dot = document.createElement("span");
|
|
5854
6415
|
dot.className = "pending-dot";
|
|
@@ -5856,6 +6417,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5856
6417
|
}
|
|
5857
6418
|
this.#pending = pending;
|
|
5858
6419
|
this.#messages.appendChild(pending);
|
|
6420
|
+
this.#updateEmptyState();
|
|
5859
6421
|
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
5860
6422
|
}
|
|
5861
6423
|
/** Remove the pending indicator if shown. */
|
|
@@ -5885,9 +6447,10 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
5885
6447
|
}
|
|
5886
6448
|
const labelled = this.#resolveTool(call.name)?.parameters[X_SUMMARY_KEY];
|
|
5887
6449
|
const summary = typeof labelled === "string" ? labelled : this.toolSummaries[call.name] ?? this.#toolCatalog[call.name] ?? prettifyToolName(call.name);
|
|
5888
|
-
const card = new ToolCallCard(call.name, call.args, this.toolDisplay, summary);
|
|
6450
|
+
const card = new ToolCallCard(call.name, call.args, this.toolDisplay, summary, this.#strings);
|
|
5889
6451
|
this.#toolCards.set(call.id, card);
|
|
5890
6452
|
this.#messages.appendChild(card.element);
|
|
6453
|
+
this.#updateEmptyState();
|
|
5891
6454
|
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
5892
6455
|
return card;
|
|
5893
6456
|
}
|
|
@@ -5900,130 +6463,6 @@ function defineAgUiChat() {
|
|
|
5900
6463
|
}
|
|
5901
6464
|
}
|
|
5902
6465
|
|
|
5903
|
-
// src/dom/native_setter.ts
|
|
5904
|
-
function prototypeSetter(proto, prop) {
|
|
5905
|
-
const setter = Object.getOwnPropertyDescriptor(proto, prop).set;
|
|
5906
|
-
return setter;
|
|
5907
|
-
}
|
|
5908
|
-
var setInputValue = prototypeSetter(HTMLInputElement.prototype, "value");
|
|
5909
|
-
var setTextareaValue = prototypeSetter(HTMLTextAreaElement.prototype, "value");
|
|
5910
|
-
var setSelectValue = prototypeSetter(HTMLSelectElement.prototype, "value");
|
|
5911
|
-
var setInputChecked = prototypeSetter(HTMLInputElement.prototype, "checked");
|
|
5912
|
-
function setNativeValue(el, value) {
|
|
5913
|
-
if (el instanceof HTMLTextAreaElement) {
|
|
5914
|
-
setTextareaValue.call(el, value);
|
|
5915
|
-
} else if (el instanceof HTMLSelectElement) {
|
|
5916
|
-
setSelectValue.call(el, value);
|
|
5917
|
-
} else {
|
|
5918
|
-
setInputValue.call(el, value);
|
|
5919
|
-
}
|
|
5920
|
-
}
|
|
5921
|
-
function setNativeChecked(el, checked) {
|
|
5922
|
-
setInputChecked.call(el, checked);
|
|
5923
|
-
}
|
|
5924
|
-
|
|
5925
|
-
// src/dom/animations.ts
|
|
5926
|
-
var ACCENT = "#4f46e5";
|
|
5927
|
-
function delay(ms) {
|
|
5928
|
-
return new Promise((resolve) => {
|
|
5929
|
-
setTimeout(resolve, ms);
|
|
5930
|
-
});
|
|
5931
|
-
}
|
|
5932
|
-
async function typeInto(el, value, options = {}) {
|
|
5933
|
-
const charDelayMs = options.charDelayMs ?? 35;
|
|
5934
|
-
setNativeValue(el, "");
|
|
5935
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5936
|
-
for (const char of value) {
|
|
5937
|
-
setNativeValue(el, el.value + char);
|
|
5938
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
5939
|
-
if (charDelayMs > 0) {
|
|
5940
|
-
await delay(charDelayMs);
|
|
5941
|
-
}
|
|
5942
|
-
}
|
|
5943
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
5944
|
-
}
|
|
5945
|
-
async function highlightThenClick(el, options = {}) {
|
|
5946
|
-
const highlightMs = options.highlightMs ?? 280;
|
|
5947
|
-
const previousOutline = el.style.outline;
|
|
5948
|
-
const previousOffset = el.style.outlineOffset;
|
|
5949
|
-
el.style.outline = `2px solid ${ACCENT}`;
|
|
5950
|
-
el.style.outlineOffset = "2px";
|
|
5951
|
-
await delay(highlightMs);
|
|
5952
|
-
el.style.outline = previousOutline;
|
|
5953
|
-
el.style.outlineOffset = previousOffset;
|
|
5954
|
-
el.click();
|
|
5955
|
-
}
|
|
5956
|
-
function scrollIntoCenterView(el) {
|
|
5957
|
-
el.scrollIntoView({ block: "center", inline: "nearest", behavior: "smooth" });
|
|
5958
|
-
}
|
|
5959
|
-
async function focusWithFlash(el, options = {}) {
|
|
5960
|
-
const flashMs = options.flashMs ?? 200;
|
|
5961
|
-
el.focus();
|
|
5962
|
-
const previousShadow = el.style.boxShadow;
|
|
5963
|
-
el.style.boxShadow = `0 0 0 3px rgba(79, 70, 229, 0.4)`;
|
|
5964
|
-
await delay(flashMs);
|
|
5965
|
-
el.style.boxShadow = previousShadow;
|
|
5966
|
-
}
|
|
5967
|
-
var RING = "0 0 0 3px rgba(79, 70, 229, 0.4)";
|
|
5968
|
-
function prefersReducedMotion() {
|
|
5969
|
-
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
5970
|
-
}
|
|
5971
|
-
function motionDelay(ms) {
|
|
5972
|
-
if (ms <= 0 || prefersReducedMotion()) {
|
|
5973
|
-
return Promise.resolve();
|
|
5974
|
-
}
|
|
5975
|
-
return delay(ms);
|
|
5976
|
-
}
|
|
5977
|
-
async function pressThenClick(el, options = {}) {
|
|
5978
|
-
const pressMs = options.pressMs ?? 140;
|
|
5979
|
-
const previousTransform = el.style.transform;
|
|
5980
|
-
const previousTransition = el.style.transition;
|
|
5981
|
-
const previousShadow = el.style.boxShadow;
|
|
5982
|
-
el.style.transition = "transform 80ms ease";
|
|
5983
|
-
el.style.transform = "scale(0.96)";
|
|
5984
|
-
el.style.boxShadow = RING;
|
|
5985
|
-
await motionDelay(pressMs);
|
|
5986
|
-
el.style.transform = previousTransform;
|
|
5987
|
-
el.style.transition = previousTransition;
|
|
5988
|
-
el.style.boxShadow = previousShadow;
|
|
5989
|
-
el.click();
|
|
5990
|
-
}
|
|
5991
|
-
function findOption(el, value) {
|
|
5992
|
-
for (const option of Array.from(el.options)) {
|
|
5993
|
-
if (option.value === value || option.text === value) {
|
|
5994
|
-
return option;
|
|
5995
|
-
}
|
|
5996
|
-
}
|
|
5997
|
-
return null;
|
|
5998
|
-
}
|
|
5999
|
-
async function selectOption(el, value, options = {}) {
|
|
6000
|
-
const option = findOption(el, value);
|
|
6001
|
-
if (option === null) {
|
|
6002
|
-
throw new Error(`no <option> matching "${value}"`);
|
|
6003
|
-
}
|
|
6004
|
-
const highlightMs = options.highlightMs ?? 220;
|
|
6005
|
-
const previousOutline = el.style.outline;
|
|
6006
|
-
const previousOffset = el.style.outlineOffset;
|
|
6007
|
-
el.style.outline = `2px solid ${ACCENT}`;
|
|
6008
|
-
el.style.outlineOffset = "2px";
|
|
6009
|
-
await motionDelay(highlightMs);
|
|
6010
|
-
setNativeValue(el, option.value);
|
|
6011
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
6012
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
6013
|
-
el.style.outline = previousOutline;
|
|
6014
|
-
el.style.outlineOffset = previousOffset;
|
|
6015
|
-
}
|
|
6016
|
-
async function toggleControl(el, checked, options = {}) {
|
|
6017
|
-
const flashMs = options.flashMs ?? 200;
|
|
6018
|
-
const previousShadow = el.style.boxShadow;
|
|
6019
|
-
el.style.boxShadow = RING;
|
|
6020
|
-
await motionDelay(flashMs);
|
|
6021
|
-
setNativeChecked(el, checked);
|
|
6022
|
-
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
6023
|
-
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
6024
|
-
el.style.boxShadow = previousShadow;
|
|
6025
|
-
}
|
|
6026
|
-
|
|
6027
6466
|
// src/dom/dom_driver.ts
|
|
6028
6467
|
async function fillField(el, value, options = {}) {
|
|
6029
6468
|
scrollIntoCenterView(el);
|
|
@@ -6057,14 +6496,17 @@ function setControlValue(el, value) {
|
|
|
6057
6496
|
}
|
|
6058
6497
|
|
|
6059
6498
|
// src/version.ts
|
|
6060
|
-
var VERSION = "0.
|
|
6499
|
+
var VERSION = "0.7.0";
|
|
6061
6500
|
export {
|
|
6062
6501
|
AgUiChat,
|
|
6063
6502
|
AgUiClient,
|
|
6064
6503
|
ClientToolRegistry,
|
|
6504
|
+
ConnectionLostError,
|
|
6505
|
+
DEFAULT_UI_STRINGS,
|
|
6065
6506
|
ELEMENT_TAG,
|
|
6066
6507
|
MAX_TOOL_ROUNDS,
|
|
6067
6508
|
MESSAGE_ROLE,
|
|
6509
|
+
PAGE_ACTIONS,
|
|
6068
6510
|
RemoteConversationStore,
|
|
6069
6511
|
SUBMIT_EVENT,
|
|
6070
6512
|
SessionStorageStore,
|
|
@@ -6079,6 +6521,7 @@ export {
|
|
|
6079
6521
|
X_SUMMARY_KEY,
|
|
6080
6522
|
clickElement,
|
|
6081
6523
|
createHttpAgent,
|
|
6524
|
+
createPageActionTools,
|
|
6082
6525
|
createPageMapContext,
|
|
6083
6526
|
createRouteTools,
|
|
6084
6527
|
createStateHookTools,
|
|
@@ -6088,6 +6531,7 @@ export {
|
|
|
6088
6531
|
highlightThenClick,
|
|
6089
6532
|
isDestructive,
|
|
6090
6533
|
isNavigates,
|
|
6534
|
+
mergeUiStrings,
|
|
6091
6535
|
messageAttachments,
|
|
6092
6536
|
parseToolCatalog,
|
|
6093
6537
|
prefersReducedMotion,
|