@artooi/ag-ui-web-component 0.18.0 → 0.20.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 +165 -1
- package/README.md +86 -4
- package/dist/ag-ui-web-component.bundle.js +256 -47
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/core/ag_ui_chat.d.ts +5 -1
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/index.js +579 -69
- package/dist/index.js.map +4 -4
- package/dist/skills/parse_skills.d.ts.map +1 -1
- package/dist/skills/skill.d.ts +14 -4
- package/dist/skills/skill.d.ts.map +1 -1
- package/dist/ui/confirmation_card.d.ts +8 -2
- package/dist/ui/confirmation_card.d.ts.map +1 -1
- package/dist/ui/resize_handle.d.ts +81 -0
- package/dist/ui/resize_handle.d.ts.map +1 -0
- package/dist/ui/skills_menu.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/tool_call_card.d.ts +26 -14
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +12 -4
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/ag_ui_chat.ts +230 -10
- package/src/core/agui_client.ts +27 -1
- package/src/skills/parse_skills.ts +8 -2
- package/src/skills/skill.ts +14 -4
- package/src/ui/confirmation_card.ts +15 -5
- package/src/ui/resize_handle.ts +176 -0
- package/src/ui/skills_menu.ts +13 -1
- package/src/ui/styles.ts +217 -6
- package/src/ui/tool_call_card.ts +116 -58
- package/src/ui/ui_strings.ts +17 -5
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -58,7 +58,7 @@ function isSkill(value) {
|
|
|
58
58
|
return false;
|
|
59
59
|
}
|
|
60
60
|
const record = value;
|
|
61
|
-
return typeof record["name"] === "string" && typeof record["title"] === "string" && typeof record["prompt"] === "string";
|
|
61
|
+
return typeof record["name"] === "string" && typeof record["title"] === "string" && (record["prompt"] === void 0 || typeof record["prompt"] === "string");
|
|
62
62
|
}
|
|
63
63
|
function parseSkills(value) {
|
|
64
64
|
if (!Array.isArray(value)) {
|
|
@@ -471,7 +471,7 @@ var DEFAULT_UI_STRINGS = {
|
|
|
471
471
|
runInterrupted: "The previous response didn\u2019t finish \u2014 the page changed before it arrived.",
|
|
472
472
|
pageMoved: "The page changed since you last looked at it. Call read_page to see the current page, then retry.",
|
|
473
473
|
attachmentsStillUploading: "{n} file still uploading \u2014 it was not sent with this message and is still attached.",
|
|
474
|
-
skillNeeds: "\u201C{title}\u201D needs
|
|
474
|
+
skillNeeds: "\u201C{title}\u201D needs {fields} \u2014 fill it in below, then send.",
|
|
475
475
|
message: "Message",
|
|
476
476
|
inputPlaceholder: "Ask anything\u2026",
|
|
477
477
|
send: "Send",
|
|
@@ -485,6 +485,10 @@ var DEFAULT_UI_STRINGS = {
|
|
|
485
485
|
toolDone: "\u2713 done",
|
|
486
486
|
toolError: "\u26A0 error",
|
|
487
487
|
toolDeclined: "\u2298 declined",
|
|
488
|
+
resizePanel: "Resize the chat panel",
|
|
489
|
+
decisionApproved: "approved by you",
|
|
490
|
+
decisionDeclined: "declined by you",
|
|
491
|
+
argumentsLabel: "Arguments",
|
|
488
492
|
resultLabel: "Result",
|
|
489
493
|
errorLabel: "Error",
|
|
490
494
|
declinedLabel: "Declined",
|
|
@@ -1106,6 +1110,7 @@ function requestConfirmation(host, request, options = {}) {
|
|
|
1106
1110
|
args.className = "confirm-args";
|
|
1107
1111
|
args.setAttribute("part", "confirm-args");
|
|
1108
1112
|
args.textContent = JSON.stringify(request.args, null, 2);
|
|
1113
|
+
args.hidden = Object.keys(request.args).length === 0;
|
|
1109
1114
|
const actions = document.createElement("div");
|
|
1110
1115
|
actions.className = "confirm-actions";
|
|
1111
1116
|
actions.setAttribute("part", "confirm-actions");
|
|
@@ -1117,9 +1122,7 @@ function requestConfirmation(host, request, options = {}) {
|
|
|
1117
1122
|
return;
|
|
1118
1123
|
}
|
|
1119
1124
|
settled = true;
|
|
1120
|
-
|
|
1121
|
-
confirm2.disabled = true;
|
|
1122
|
-
card.setAttribute("data-resolved", accepted ? "confirmed" : "declined");
|
|
1125
|
+
card.remove();
|
|
1123
1126
|
resolve(accepted);
|
|
1124
1127
|
};
|
|
1125
1128
|
cancel.addEventListener("click", () => close(false));
|
|
@@ -4119,6 +4122,76 @@ function renderMarkdown(text2, options) {
|
|
|
4119
4122
|
return template.innerHTML.trim();
|
|
4120
4123
|
}
|
|
4121
4124
|
|
|
4125
|
+
// src/ui/resize_handle.ts
|
|
4126
|
+
var MIN_WIDTH = 280;
|
|
4127
|
+
var MIN_HEIGHT = 240;
|
|
4128
|
+
function createResizeHandle(options) {
|
|
4129
|
+
const handle = document.createElement("div");
|
|
4130
|
+
handle.className = "resize-handle";
|
|
4131
|
+
handle.setAttribute("part", "resize-handle");
|
|
4132
|
+
handle.setAttribute("role", "separator");
|
|
4133
|
+
handle.setAttribute("aria-label", options.label);
|
|
4134
|
+
handle.tabIndex = 0;
|
|
4135
|
+
const sizeAt = (axis, anchor, rect, x2, y2) => {
|
|
4136
|
+
const width = anchor.x === "right" ? rect.right - x2 : x2 - rect.left;
|
|
4137
|
+
const clamped = { width: Math.max(MIN_WIDTH, width) };
|
|
4138
|
+
if (axis !== "both") {
|
|
4139
|
+
return clamped;
|
|
4140
|
+
}
|
|
4141
|
+
const height = anchor.y === "bottom" ? rect.bottom - y2 : y2 - rect.top;
|
|
4142
|
+
return { ...clamped, height: Math.max(MIN_HEIGHT, height) };
|
|
4143
|
+
};
|
|
4144
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
4145
|
+
const axis = options.axis();
|
|
4146
|
+
if (axis === "none") {
|
|
4147
|
+
return;
|
|
4148
|
+
}
|
|
4149
|
+
const anchor = options.anchor();
|
|
4150
|
+
const rect = options.rect();
|
|
4151
|
+
const onMove = (move) => {
|
|
4152
|
+
options.apply(sizeAt(axis, anchor, rect, move.clientX, move.clientY));
|
|
4153
|
+
};
|
|
4154
|
+
const onUp = (up) => {
|
|
4155
|
+
window.removeEventListener("pointermove", onMove);
|
|
4156
|
+
window.removeEventListener("pointerup", onUp);
|
|
4157
|
+
handle.removeAttribute("data-dragging");
|
|
4158
|
+
options.commit(sizeAt(axis, anchor, rect, up.clientX, up.clientY));
|
|
4159
|
+
};
|
|
4160
|
+
handle.setAttribute("data-dragging", "true");
|
|
4161
|
+
window.addEventListener("pointermove", onMove);
|
|
4162
|
+
window.addEventListener("pointerup", onUp);
|
|
4163
|
+
event.preventDefault();
|
|
4164
|
+
});
|
|
4165
|
+
handle.addEventListener("keydown", (event) => {
|
|
4166
|
+
const axis = options.axis();
|
|
4167
|
+
if (axis === "none") {
|
|
4168
|
+
return;
|
|
4169
|
+
}
|
|
4170
|
+
const anchor = options.anchor();
|
|
4171
|
+
const rect = options.rect();
|
|
4172
|
+
const step = event.shiftKey ? 64 : 16;
|
|
4173
|
+
const outward = anchor.x === "right" ? -1 : 1;
|
|
4174
|
+
const width = rect.right - rect.left;
|
|
4175
|
+
const height = rect.bottom - rect.top;
|
|
4176
|
+
let next = null;
|
|
4177
|
+
if (event.key === "ArrowLeft") {
|
|
4178
|
+
next = { width: Math.max(MIN_WIDTH, width - step * outward) };
|
|
4179
|
+
} else if (event.key === "ArrowRight") {
|
|
4180
|
+
next = { width: Math.max(MIN_WIDTH, width + step * outward) };
|
|
4181
|
+
} else if (axis === "both" && (event.key === "ArrowUp" || event.key === "ArrowDown")) {
|
|
4182
|
+
const grow = event.key === (anchor.y === "bottom" ? "ArrowUp" : "ArrowDown");
|
|
4183
|
+
next = { height: Math.max(MIN_HEIGHT, height + (grow ? step : -step)) };
|
|
4184
|
+
}
|
|
4185
|
+
if (next === null) {
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
event.preventDefault();
|
|
4189
|
+
options.apply(next);
|
|
4190
|
+
options.commit(next);
|
|
4191
|
+
});
|
|
4192
|
+
return handle;
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4122
4195
|
// src/ui/reveal_words.ts
|
|
4123
4196
|
function collectTextNodes(root, out) {
|
|
4124
4197
|
for (const child of Array.from(root.childNodes)) {
|
|
@@ -4290,6 +4363,7 @@ var SkillsMenu = class {
|
|
|
4290
4363
|
button2.className = "skill-chip";
|
|
4291
4364
|
button2.setAttribute("part", "skill-chip");
|
|
4292
4365
|
button2.textContent = skill.title;
|
|
4366
|
+
button2.title = `/${skill.name}`;
|
|
4293
4367
|
button2.addEventListener("click", () => this.#pick(skill));
|
|
4294
4368
|
this.chips.appendChild(button2);
|
|
4295
4369
|
}
|
|
@@ -4306,7 +4380,11 @@ var SkillsMenu = class {
|
|
|
4306
4380
|
const title = document.createElement("span");
|
|
4307
4381
|
title.className = "skill-item-title";
|
|
4308
4382
|
title.setAttribute("part", "skill-item-title");
|
|
4309
|
-
|
|
4383
|
+
const token = document.createElement("code");
|
|
4384
|
+
token.className = "skill-item-token";
|
|
4385
|
+
token.setAttribute("part", "skill-item-token");
|
|
4386
|
+
token.textContent = `/${skill.name}`;
|
|
4387
|
+
title.append(token, document.createTextNode(` ${skill.title}`));
|
|
4310
4388
|
item.appendChild(title);
|
|
4311
4389
|
if (skill.description !== void 0) {
|
|
4312
4390
|
const desc = document.createElement("span");
|
|
@@ -4488,7 +4566,72 @@ var STYLES = `
|
|
|
4488
4566
|
padding-inline: max(var(--ag-ui-pad), calc((100% - var(--ag-ui-content-max-width)) / 2));
|
|
4489
4567
|
}
|
|
4490
4568
|
|
|
4491
|
-
:host([placement="page"])
|
|
4569
|
+
:host([placement="page"]) /* Resize handle: a corner grip on a floating panel, an edge grip on a docked
|
|
4570
|
+
one. Absent entirely where the placement is full-bleed, since there is
|
|
4571
|
+
nothing to drag. */
|
|
4572
|
+
.resize-handle {
|
|
4573
|
+
position: absolute;
|
|
4574
|
+
z-index: 2;
|
|
4575
|
+
background: transparent;
|
|
4576
|
+
touch-action: none;
|
|
4577
|
+
}
|
|
4578
|
+
|
|
4579
|
+
.resize-handle:focus-visible {
|
|
4580
|
+
outline: 2px solid var(--ag-ui-accent);
|
|
4581
|
+
outline-offset: -2px;
|
|
4582
|
+
}
|
|
4583
|
+
|
|
4584
|
+
/* The grip sits at the corner the panel grows toward. Which corner that is
|
|
4585
|
+
depends on the host's layout, not on placement, so the element measures it
|
|
4586
|
+
and stamps data-resize-anchor with the edges that stay put.
|
|
4587
|
+
|
|
4588
|
+
Default (bottom-right pinned, the floating case) puts the grip top-left. */
|
|
4589
|
+
.resize-handle {
|
|
4590
|
+
top: 0;
|
|
4591
|
+
left: 0;
|
|
4592
|
+
width: 14px;
|
|
4593
|
+
height: 14px;
|
|
4594
|
+
cursor: nwse-resize;
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
:host([data-resize-anchor~="left"]) .resize-handle {
|
|
4598
|
+
left: auto;
|
|
4599
|
+
right: 0;
|
|
4600
|
+
}
|
|
4601
|
+
|
|
4602
|
+
:host([data-resize-anchor~="top"]) .resize-handle {
|
|
4603
|
+
top: auto;
|
|
4604
|
+
bottom: 0;
|
|
4605
|
+
}
|
|
4606
|
+
|
|
4607
|
+
/* One axis flipped means the drag runs along the other diagonal. */
|
|
4608
|
+
:host([data-resize-anchor="top-right"]) .resize-handle,
|
|
4609
|
+
:host([data-resize-anchor="bottom-left"]) .resize-handle {
|
|
4610
|
+
cursor: nesw-resize;
|
|
4611
|
+
}
|
|
4612
|
+
|
|
4613
|
+
/* Docked: the placement owns the height, so only the inner edge is the user's. */
|
|
4614
|
+
:host([placement="sidebar"]) .resize-handle,
|
|
4615
|
+
:host([placement="side"]) .resize-handle {
|
|
4616
|
+
top: 0;
|
|
4617
|
+
bottom: auto;
|
|
4618
|
+
width: 8px;
|
|
4619
|
+
height: 100%;
|
|
4620
|
+
cursor: ew-resize;
|
|
4621
|
+
}
|
|
4622
|
+
|
|
4623
|
+
/* Full-bleed: nothing to drag. */
|
|
4624
|
+
:host([placement="full"]) .resize-handle,
|
|
4625
|
+
:host([placement="page"]) .resize-handle {
|
|
4626
|
+
display: none;
|
|
4627
|
+
}
|
|
4628
|
+
|
|
4629
|
+
.resize-handle[data-dragging] {
|
|
4630
|
+
background: var(--ag-ui-accent);
|
|
4631
|
+
opacity: 0.35;
|
|
4632
|
+
}
|
|
4633
|
+
|
|
4634
|
+
.input-row {
|
|
4492
4635
|
padding-inline: max(12px, calc((100% - var(--ag-ui-content-max-width)) / 2));
|
|
4493
4636
|
}
|
|
4494
4637
|
|
|
@@ -5092,10 +5235,9 @@ var STYLES = `
|
|
|
5092
5235
|
}
|
|
5093
5236
|
}
|
|
5094
5237
|
|
|
5095
|
-
/* Inline display mode: the lightest card
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
.tool-call[data-display="inline"] {
|
|
5238
|
+
/* Inline display mode: the lightest card. Drop the box chrome so the status row
|
|
5239
|
+
reads as one line of the answer; the result toggle still expands below it. */
|
|
5240
|
+
:host([data-tool-display="inline"]) .tool-call {
|
|
5099
5241
|
max-width: 100%;
|
|
5100
5242
|
background: transparent;
|
|
5101
5243
|
border: none;
|
|
@@ -5125,6 +5267,44 @@ var STYLES = `
|
|
|
5125
5267
|
color: var(--ag-ui-muted);
|
|
5126
5268
|
}
|
|
5127
5269
|
|
|
5270
|
+
.tool-call-body {
|
|
5271
|
+
display: flex;
|
|
5272
|
+
flex-direction: column;
|
|
5273
|
+
gap: 6px;
|
|
5274
|
+
}
|
|
5275
|
+
|
|
5276
|
+
.tool-call-section {
|
|
5277
|
+
display: flex;
|
|
5278
|
+
flex-direction: column;
|
|
5279
|
+
gap: 3px;
|
|
5280
|
+
}
|
|
5281
|
+
|
|
5282
|
+
/* The heading that tells the two payloads apart. Without it the arguments and
|
|
5283
|
+
the result were one run of text and a reader had to guess the boundary. */
|
|
5284
|
+
.tool-call-section-label {
|
|
5285
|
+
font-size: 10px;
|
|
5286
|
+
font-weight: 700;
|
|
5287
|
+
letter-spacing: 0.06em;
|
|
5288
|
+
text-transform: uppercase;
|
|
5289
|
+
color: var(--ag-ui-muted);
|
|
5290
|
+
}
|
|
5291
|
+
|
|
5292
|
+
/* The record of a human decision on a gated call. An approved call used to
|
|
5293
|
+
look exactly like one that was never gated. */
|
|
5294
|
+
.skill-item-token {
|
|
5295
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
5296
|
+
font-size: 0.92em;
|
|
5297
|
+
color: var(--ag-ui-accent);
|
|
5298
|
+
margin-right: 6px;
|
|
5299
|
+
}
|
|
5300
|
+
|
|
5301
|
+
.tool-call-decision {
|
|
5302
|
+
flex: none;
|
|
5303
|
+
font-size: 11px;
|
|
5304
|
+
font-style: italic;
|
|
5305
|
+
color: var(--ag-ui-muted);
|
|
5306
|
+
}
|
|
5307
|
+
|
|
5128
5308
|
.tool-call-args,
|
|
5129
5309
|
.tool-call-result {
|
|
5130
5310
|
margin: 0;
|
|
@@ -5139,6 +5319,43 @@ var STYLES = `
|
|
|
5139
5319
|
color: var(--ag-ui-fg);
|
|
5140
5320
|
}
|
|
5141
5321
|
|
|
5322
|
+
/* Display modes are pure visibility over one DOM shape, selected from the host
|
|
5323
|
+
attribute rather than a value stamped on the card when it was built. That is
|
|
5324
|
+
what lets a host flip data-tool-display and have every card already on screen
|
|
5325
|
+
re-read it, the way data-answer-well behaves. Baking the structure per mode
|
|
5326
|
+
meant the setting only reached cards created afterwards.
|
|
5327
|
+
|
|
5328
|
+
Default (no attribute) is the full mode: arguments always visible, result
|
|
5329
|
+
behind the toggle. */
|
|
5330
|
+
.tool-call[data-expanded="false"] .tool-call-section--result {
|
|
5331
|
+
display: none;
|
|
5332
|
+
}
|
|
5333
|
+
|
|
5334
|
+
/* Compact: one toggle over both regions, so a settled card is a single line
|
|
5335
|
+
until asked. */
|
|
5336
|
+
:host([data-tool-display="compact"]) .tool-call[data-expanded="false"] .tool-call-section {
|
|
5337
|
+
display: none;
|
|
5338
|
+
}
|
|
5339
|
+
|
|
5340
|
+
/* Inline: the result only; the call's arguments are noise at this density. */
|
|
5341
|
+
:host([data-tool-display="inline"]) .tool-call .tool-call-section--args {
|
|
5342
|
+
display: none;
|
|
5343
|
+
}
|
|
5344
|
+
|
|
5345
|
+
/* Minimal: the status row and nothing else, so there is no toggle to press. */
|
|
5346
|
+
:host([data-tool-display="minimal"]) .tool-call .tool-call-toggle,
|
|
5347
|
+
:host([data-tool-display="minimal"]) .tool-call .tool-call-body {
|
|
5348
|
+
display: none;
|
|
5349
|
+
}
|
|
5350
|
+
|
|
5351
|
+
/* A pending card has no result yet, and in the modes where the arguments are
|
|
5352
|
+
hidden too there is nothing behind the toggle. Hide the control rather than
|
|
5353
|
+
offer one that expands onto nothing. */
|
|
5354
|
+
.tool-call[data-status="pending"] .tool-call-toggle,
|
|
5355
|
+
:host([data-tool-display="inline"]) .tool-call[data-status="pending"] .tool-call-toggle {
|
|
5356
|
+
display: none;
|
|
5357
|
+
}
|
|
5358
|
+
|
|
5142
5359
|
.tool-call-toggle {
|
|
5143
5360
|
align-self: flex-start;
|
|
5144
5361
|
border: none;
|
|
@@ -5158,6 +5375,75 @@ var STYLES = `
|
|
|
5158
5375
|
content: "\u25BE ";
|
|
5159
5376
|
}
|
|
5160
5377
|
|
|
5378
|
+
/* Resize handle: a corner grip on a floating panel, an edge grip on a docked
|
|
5379
|
+
one. Absent entirely where the placement is full-bleed, since there is
|
|
5380
|
+
nothing to drag. */
|
|
5381
|
+
.resize-handle {
|
|
5382
|
+
position: absolute;
|
|
5383
|
+
z-index: 2;
|
|
5384
|
+
background: transparent;
|
|
5385
|
+
touch-action: none;
|
|
5386
|
+
}
|
|
5387
|
+
|
|
5388
|
+
.resize-handle:focus-visible {
|
|
5389
|
+
outline: 2px solid var(--ag-ui-accent);
|
|
5390
|
+
outline-offset: -2px;
|
|
5391
|
+
}
|
|
5392
|
+
|
|
5393
|
+
/* The grip sits on the corner opposite the panel's anchor, because a resize
|
|
5394
|
+
measures from whichever edge is not moving. Selected from the host attribute,
|
|
5395
|
+
so switching placement moves the grip immediately.
|
|
5396
|
+
|
|
5397
|
+
Default is floating: pinned bottom-right, so the grip is top-left. */
|
|
5398
|
+
.resize-handle {
|
|
5399
|
+
top: 0;
|
|
5400
|
+
left: 0;
|
|
5401
|
+
width: 14px;
|
|
5402
|
+
height: 14px;
|
|
5403
|
+
cursor: nwse-resize;
|
|
5404
|
+
}
|
|
5405
|
+
|
|
5406
|
+
/* Embedded sits in normal flow, pinned top-left, so it grows bottom-right. */
|
|
5407
|
+
:host([placement="embedded"]) .resize-handle {
|
|
5408
|
+
top: auto;
|
|
5409
|
+
left: auto;
|
|
5410
|
+
right: 0;
|
|
5411
|
+
bottom: 0;
|
|
5412
|
+
cursor: nwse-resize;
|
|
5413
|
+
}
|
|
5414
|
+
|
|
5415
|
+
/* Pinned bottom-left, so the free corner is top-right. */
|
|
5416
|
+
:host([placement="bottom-left"]) .resize-handle {
|
|
5417
|
+
left: auto;
|
|
5418
|
+
right: 0;
|
|
5419
|
+
cursor: nesw-resize;
|
|
5420
|
+
}
|
|
5421
|
+
|
|
5422
|
+
/* Docked: the placement owns the height, so only the inner edge is the user's. */
|
|
5423
|
+
:host([placement="sidebar"]) .resize-handle,
|
|
5424
|
+
:host([placement="side"]) .resize-handle {
|
|
5425
|
+
width: 8px;
|
|
5426
|
+
height: 100%;
|
|
5427
|
+
cursor: ew-resize;
|
|
5428
|
+
}
|
|
5429
|
+
|
|
5430
|
+
/* Docked to the left, so the inner edge is the right-hand one. */
|
|
5431
|
+
:host([placement="sidebar"][data-side="left"]) .resize-handle {
|
|
5432
|
+
left: auto;
|
|
5433
|
+
right: 0;
|
|
5434
|
+
}
|
|
5435
|
+
|
|
5436
|
+
/* Full-bleed: nothing to drag. */
|
|
5437
|
+
:host([placement="full"]) .resize-handle,
|
|
5438
|
+
:host([placement="page"]) .resize-handle {
|
|
5439
|
+
display: none;
|
|
5440
|
+
}
|
|
5441
|
+
|
|
5442
|
+
.resize-handle[data-dragging] {
|
|
5443
|
+
background: var(--ag-ui-accent);
|
|
5444
|
+
opacity: 0.35;
|
|
5445
|
+
}
|
|
5446
|
+
|
|
5161
5447
|
.input-row {
|
|
5162
5448
|
display: flex;
|
|
5163
5449
|
gap: 8px;
|
|
@@ -5635,9 +5921,12 @@ var STYLES = `
|
|
|
5635
5921
|
color: var(--ag-ui-muted);
|
|
5636
5922
|
}
|
|
5637
5923
|
|
|
5924
|
+
/* The hint sits directly above the composer's top border, so a zero bottom
|
|
5925
|
+
margin left the text touching the divider. */
|
|
5638
5926
|
.skill-hint {
|
|
5639
|
-
margin: 8px 12px
|
|
5927
|
+
margin: 8px 12px;
|
|
5640
5928
|
font-size: 0.85em;
|
|
5929
|
+
line-height: 1.4;
|
|
5641
5930
|
color: var(--ag-ui-danger);
|
|
5642
5931
|
}
|
|
5643
5932
|
|
|
@@ -6231,24 +6520,32 @@ function resultLabels(strings) {
|
|
|
6231
6520
|
[TOOL_CALL_STATUS.DECLINED]: strings.declinedLabel
|
|
6232
6521
|
};
|
|
6233
6522
|
}
|
|
6523
|
+
function formatPayload(text2) {
|
|
6524
|
+
try {
|
|
6525
|
+
return JSON.stringify(JSON.parse(text2), null, 2);
|
|
6526
|
+
} catch {
|
|
6527
|
+
return text2;
|
|
6528
|
+
}
|
|
6529
|
+
}
|
|
6234
6530
|
var ToolCallCard = class {
|
|
6235
6531
|
/** The card's root element; append this into the message list. */
|
|
6236
6532
|
element;
|
|
6237
6533
|
#status;
|
|
6238
|
-
#
|
|
6239
|
-
#
|
|
6534
|
+
#decision;
|
|
6535
|
+
#toggle;
|
|
6536
|
+
#resultSection;
|
|
6537
|
+
#resultLabel;
|
|
6538
|
+
#resultBody;
|
|
6240
6539
|
#strings;
|
|
6241
6540
|
#settled = false;
|
|
6242
|
-
constructor(name, args,
|
|
6243
|
-
this.#mode = mode;
|
|
6244
|
-
this.#args = args;
|
|
6541
|
+
constructor(name, args, summary, strings = DEFAULT_UI_STRINGS) {
|
|
6245
6542
|
this.#strings = strings;
|
|
6246
6543
|
this.element = document.createElement("div");
|
|
6247
6544
|
this.element.className = "tool-call";
|
|
6248
6545
|
this.element.setAttribute("part", "tool-card");
|
|
6249
6546
|
this.element.setAttribute("data-tool-name", name);
|
|
6250
6547
|
this.element.setAttribute("data-status", TOOL_CALL_STATUS.PENDING);
|
|
6251
|
-
this.element.setAttribute("data-
|
|
6548
|
+
this.element.setAttribute("data-expanded", "false");
|
|
6252
6549
|
const head = document.createElement("div");
|
|
6253
6550
|
head.className = "tool-call-head";
|
|
6254
6551
|
head.setAttribute("part", "tool-card-head");
|
|
@@ -6264,24 +6561,52 @@ var ToolCallCard = class {
|
|
|
6264
6561
|
this.#status.className = "tool-call-status";
|
|
6265
6562
|
this.#status.setAttribute("part", "tool-card-status");
|
|
6266
6563
|
this.#status.textContent = statusLabels(strings)[TOOL_CALL_STATUS.PENDING];
|
|
6267
|
-
|
|
6268
|
-
this.
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6564
|
+
this.#decision = document.createElement("span");
|
|
6565
|
+
this.#decision.className = "tool-call-decision";
|
|
6566
|
+
this.#decision.setAttribute("part", "tool-card-decision");
|
|
6567
|
+
this.#decision.hidden = true;
|
|
6568
|
+
head.append(icon, label, this.#status, this.#decision);
|
|
6569
|
+
const argsSection = this.#section("args", strings.argumentsLabel);
|
|
6570
|
+
argsSection.body.textContent = JSON.stringify(args, null, 2);
|
|
6571
|
+
argsSection.root.hidden = Object.keys(args).length === 0;
|
|
6572
|
+
const resultSection = this.#section("result", strings.resultLabel);
|
|
6573
|
+
this.#resultSection = resultSection.root;
|
|
6574
|
+
this.#resultLabel = resultSection.label;
|
|
6575
|
+
this.#resultBody = resultSection.body;
|
|
6576
|
+
resultSection.root.hidden = true;
|
|
6577
|
+
this.#toggle = document.createElement("button");
|
|
6578
|
+
this.#toggle.type = "button";
|
|
6579
|
+
this.#toggle.className = "tool-call-toggle";
|
|
6580
|
+
this.#toggle.setAttribute("part", "tool-card-toggle");
|
|
6581
|
+
this.#toggle.setAttribute("aria-expanded", "false");
|
|
6582
|
+
this.#toggle.textContent = strings.details;
|
|
6583
|
+
this.#toggle.addEventListener("click", () => this.#setExpanded(!this.#expanded()));
|
|
6584
|
+
const body = document.createElement("div");
|
|
6585
|
+
body.className = "tool-call-body";
|
|
6586
|
+
body.setAttribute("part", "tool-card-body");
|
|
6587
|
+
body.append(argsSection.root, resultSection.root);
|
|
6588
|
+
this.element.append(head, this.#toggle, body);
|
|
6589
|
+
}
|
|
6590
|
+
/**
|
|
6591
|
+
* Record that a human approved or declined this call, as a line in the card.
|
|
6592
|
+
*
|
|
6593
|
+
* Approval used to leave no trace at all: a declined call became a tool
|
|
6594
|
+
* result saying so, while an approved one simply ran, making the transcript
|
|
6595
|
+
* of a gated call byte-identical to one that was never gated. The prompt is
|
|
6596
|
+
* gone once answered, so this is where the decision lives.
|
|
6597
|
+
*/
|
|
6598
|
+
recordDecision(kind) {
|
|
6599
|
+
this.element.setAttribute("data-decision", kind);
|
|
6600
|
+
this.#decision.textContent = kind === "approved" ? this.#strings.decisionApproved : this.#strings.decisionDeclined;
|
|
6601
|
+
this.#decision.hidden = false;
|
|
6276
6602
|
}
|
|
6277
6603
|
/** Whether {@link settle} has already run (so a terminal sweep can skip it). */
|
|
6278
6604
|
get settled() {
|
|
6279
6605
|
return this.#settled;
|
|
6280
6606
|
}
|
|
6281
6607
|
/**
|
|
6282
|
-
* Flip the status pill to
|
|
6283
|
-
*
|
|
6284
|
-
* `inline`), or the args + result together (`compact`).
|
|
6608
|
+
* Flip the status pill to `status` and fill in the result region, whose
|
|
6609
|
+
* heading names the outcome (result / error / declined).
|
|
6285
6610
|
*/
|
|
6286
6611
|
settle(status, text2) {
|
|
6287
6612
|
if (this.#settled) {
|
|
@@ -6290,33 +6615,31 @@ var ToolCallCard = class {
|
|
|
6290
6615
|
this.#settled = true;
|
|
6291
6616
|
this.element.setAttribute("data-status", status);
|
|
6292
6617
|
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
});
|
|
6319
|
-
this.element.append(toggle, output);
|
|
6618
|
+
this.#resultLabel.textContent = resultLabels(this.#strings)[status];
|
|
6619
|
+
this.#resultBody.textContent = formatPayload(text2);
|
|
6620
|
+
this.#resultSection.hidden = false;
|
|
6621
|
+
}
|
|
6622
|
+
/** Build one labelled region of the body: a heading plus a payload block. */
|
|
6623
|
+
#section(kind, labelText) {
|
|
6624
|
+
const root = document.createElement("div");
|
|
6625
|
+
root.className = `tool-call-section tool-call-section--${kind}`;
|
|
6626
|
+
root.setAttribute("part", `tool-card-section tool-card-${kind}-section`);
|
|
6627
|
+
const label = document.createElement("span");
|
|
6628
|
+
label.className = "tool-call-section-label";
|
|
6629
|
+
label.setAttribute("part", `tool-card-section-label tool-card-${kind}-label`);
|
|
6630
|
+
label.textContent = labelText;
|
|
6631
|
+
const body = document.createElement("pre");
|
|
6632
|
+
body.className = `tool-call-${kind}`;
|
|
6633
|
+
body.setAttribute("part", `tool-card-${kind}`);
|
|
6634
|
+
root.append(label, body);
|
|
6635
|
+
return { root, label, body };
|
|
6636
|
+
}
|
|
6637
|
+
#expanded() {
|
|
6638
|
+
return this.element.getAttribute("data-expanded") === "true";
|
|
6639
|
+
}
|
|
6640
|
+
#setExpanded(expand) {
|
|
6641
|
+
this.element.setAttribute("data-expanded", String(expand));
|
|
6642
|
+
this.#toggle.setAttribute("aria-expanded", String(expand));
|
|
6320
6643
|
}
|
|
6321
6644
|
};
|
|
6322
6645
|
|
|
@@ -6467,6 +6790,13 @@ var AgUiClient = class {
|
|
|
6467
6790
|
#executeTool;
|
|
6468
6791
|
#resolveInterrupts;
|
|
6469
6792
|
#onPersist;
|
|
6793
|
+
/**
|
|
6794
|
+
* Message ids the server has already closed, so a reuse can be reported.
|
|
6795
|
+
*
|
|
6796
|
+
* Per client rather than per run: the merge happens across runs, which is the
|
|
6797
|
+
* case a per-run set would miss entirely.
|
|
6798
|
+
*/
|
|
6799
|
+
#closedMessageIds = /* @__PURE__ */ new Set();
|
|
6470
6800
|
#connectionLostMessage;
|
|
6471
6801
|
// Set by cancel(); reset at the top of each #run(). Checked by the loop so
|
|
6472
6802
|
// a cancel between frontend-tool rounds doesn't start another round.
|
|
@@ -6638,14 +6968,23 @@ var AgUiClient = class {
|
|
|
6638
6968
|
}
|
|
6639
6969
|
#buildSubscriber(pending, runState) {
|
|
6640
6970
|
const h = this.#handlers;
|
|
6971
|
+
const closed = this.#closedMessageIds;
|
|
6641
6972
|
return {
|
|
6642
6973
|
onRunInitialized() {
|
|
6643
6974
|
h.onRunStart();
|
|
6644
6975
|
},
|
|
6976
|
+
onTextMessageStartEvent({ event }) {
|
|
6977
|
+
if (closed.has(event.messageId)) {
|
|
6978
|
+
console.warn(
|
|
6979
|
+
`<ag-ui-chat>: the server reused message id "${event.messageId}", which was already closed. Its content will be appended to that earlier message rather than starting a new one, and the merged result is what gets persisted. Issue a fresh id per message.`
|
|
6980
|
+
);
|
|
6981
|
+
}
|
|
6982
|
+
},
|
|
6645
6983
|
onTextMessageContentEvent({ textMessageBuffer }) {
|
|
6646
6984
|
h.onTextDelta(textMessageBuffer);
|
|
6647
6985
|
},
|
|
6648
|
-
onTextMessageEndEvent({ textMessageBuffer }) {
|
|
6986
|
+
onTextMessageEndEvent({ event, textMessageBuffer }) {
|
|
6987
|
+
closed.add(event.messageId);
|
|
6649
6988
|
h.onTextEnd(textMessageBuffer);
|
|
6650
6989
|
},
|
|
6651
6990
|
onToolCallEndEvent({ event, toolCallName, toolCallArgs }) {
|
|
@@ -7193,6 +7532,7 @@ var CONNECT_TIME_ATTRIBUTES = [
|
|
|
7193
7532
|
"data-icon-url"
|
|
7194
7533
|
];
|
|
7195
7534
|
var COLLAPSED_KEY = "ag-ui-chat:collapsed";
|
|
7535
|
+
var SIZE_KEY = "ag-ui-chat:size";
|
|
7196
7536
|
var THEME_KEY = "ag-ui-chat:theme";
|
|
7197
7537
|
var AgUiChat = class extends HTMLElement {
|
|
7198
7538
|
/** Agent factory; override to inject a custom or fake agent (tests). */
|
|
@@ -7541,9 +7881,13 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7541
7881
|
}
|
|
7542
7882
|
/** Attributes the element reacts to after it has been connected. */
|
|
7543
7883
|
static get observedAttributes() {
|
|
7544
|
-
return ["title-text", ...CONNECT_TIME_ATTRIBUTES];
|
|
7884
|
+
return ["title-text", "placement", ...CONNECT_TIME_ATTRIBUTES];
|
|
7545
7885
|
}
|
|
7546
7886
|
attributeChangedCallback(name, previous, value) {
|
|
7887
|
+
if (name === "placement") {
|
|
7888
|
+
requestAnimationFrame(() => this.#syncResizeAnchor());
|
|
7889
|
+
return;
|
|
7890
|
+
}
|
|
7547
7891
|
if (name === "title-text") {
|
|
7548
7892
|
this.#title.textContent = value ?? this.#strings.title;
|
|
7549
7893
|
return;
|
|
@@ -7731,7 +8075,11 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7731
8075
|
}
|
|
7732
8076
|
/**
|
|
7733
8077
|
* How much detail tool-call cards show, from the `data-tool-display`
|
|
7734
|
-
* attribute (`minimal` / `compact` / `full`). Defaults to `full`.
|
|
8078
|
+
* attribute (`minimal` / `inline` / `compact` / `full`). Defaults to `full`.
|
|
8079
|
+
*
|
|
8080
|
+
* Applied by the shadow CSS from the attribute itself, so changing it
|
|
8081
|
+
* restyles every card already in the transcript rather than only the ones
|
|
8082
|
+
* built afterwards.
|
|
7735
8083
|
*/
|
|
7736
8084
|
get toolDisplay() {
|
|
7737
8085
|
const attr = this.getAttribute("data-tool-display");
|
|
@@ -7745,6 +8093,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
7745
8093
|
}
|
|
7746
8094
|
connectedCallback() {
|
|
7747
8095
|
this.#storageNs = this.id !== "" ? this.id : this.endpoint;
|
|
8096
|
+
this.#applySize(this.#readSize());
|
|
8097
|
+
requestAnimationFrame(() => this.#syncResizeAnchor());
|
|
7748
8098
|
this.#strings = mergeUiStrings({ ...this.#readStringOverrides(), ...this.strings });
|
|
7749
8099
|
if (this.getAttribute("data-theme-toggle") !== null) {
|
|
7750
8100
|
const saved = this.#readScopedItem(THEME_KEY);
|
|
@@ -8020,21 +8370,57 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8020
8370
|
}
|
|
8021
8371
|
this.#skillsMenu.setSkills([...merged.values()]);
|
|
8022
8372
|
}
|
|
8023
|
-
/**
|
|
8373
|
+
/**
|
|
8374
|
+
* Act on a picked skill.
|
|
8375
|
+
*
|
|
8376
|
+
* A skill that ships no `prompt` is **server-resolved**: the catalog carries
|
|
8377
|
+
* only its name and label, and picking it sends the bare `/name` token for
|
|
8378
|
+
* the agent to expand — from the harness `Skills` capability, or from the
|
|
8379
|
+
* server's own instructions. That is the shape to prefer, because the prompt
|
|
8380
|
+
* then never reaches the browser at all: a skill is often where a project's
|
|
8381
|
+
* internal workflow is written down most plainly, and a catalog endpoint is a
|
|
8382
|
+
* plain GET.
|
|
8383
|
+
*
|
|
8384
|
+
* A skill that does carry a `prompt` keeps the older behaviour — the client
|
|
8385
|
+
* fills its `{placeholder}`s from the page and sends (or pre-fills) the text.
|
|
8386
|
+
* Right for a user-facing convenience, and for placeholders only the page can
|
|
8387
|
+
* supply.
|
|
8388
|
+
*
|
|
8389
|
+
* Either way a pick now **sends**, rather than parking text in the composer
|
|
8390
|
+
* for a second click; `sendImmediately: false` opts back into pre-filling.
|
|
8391
|
+
*/
|
|
8024
8392
|
#applySkill(skill) {
|
|
8393
|
+
if (skill.prompt === void 0) {
|
|
8394
|
+
this.#skillHint.hidden = true;
|
|
8395
|
+
void this.sendMessage(`/${skill.name}`);
|
|
8396
|
+
return;
|
|
8397
|
+
}
|
|
8025
8398
|
const { text: text2, missing } = fillTemplate(skill.prompt, this.skillContext());
|
|
8026
8399
|
if (missing.length > 0) {
|
|
8027
8400
|
this.#skillHint.textContent = this.#strings.skillNeeds.replace("{title}", skill.title).replace("{fields}", missing.join(", "));
|
|
8028
8401
|
this.#skillHint.hidden = false;
|
|
8402
|
+
this.#input.value = text2;
|
|
8403
|
+
this.#input.focus();
|
|
8404
|
+
this.#selectFirstPlaceholder(text2);
|
|
8029
8405
|
return;
|
|
8030
8406
|
}
|
|
8031
8407
|
this.#skillHint.hidden = true;
|
|
8032
8408
|
this.#input.value = text2;
|
|
8033
|
-
if (skill.sendImmediately ===
|
|
8034
|
-
void this.#submit();
|
|
8035
|
-
} else {
|
|
8409
|
+
if (skill.sendImmediately === false) {
|
|
8036
8410
|
this.#input.focus();
|
|
8411
|
+
return;
|
|
8037
8412
|
}
|
|
8413
|
+
void this.#submit();
|
|
8414
|
+
}
|
|
8415
|
+
/**
|
|
8416
|
+
* Put the caret on the first unresolved placeholder, selected.
|
|
8417
|
+
*
|
|
8418
|
+
* Typing then replaces it, which is the shortest path from "this skill needs
|
|
8419
|
+
* a topic" to a sendable prompt.
|
|
8420
|
+
*/
|
|
8421
|
+
#selectFirstPlaceholder(text2) {
|
|
8422
|
+
const start = text2.indexOf("{");
|
|
8423
|
+
this.#input.setSelectionRange(start, text2.indexOf("}", start) + 1);
|
|
8038
8424
|
}
|
|
8039
8425
|
/** Whether the widget is collapsed (reflected as the `collapsed` attribute). */
|
|
8040
8426
|
get collapsed() {
|
|
@@ -8082,6 +8468,102 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8082
8468
|
sessionStorage.setItem(this.#storageKey(THEME_KEY), next);
|
|
8083
8469
|
this.#syncThemeGlyph();
|
|
8084
8470
|
}
|
|
8471
|
+
/**
|
|
8472
|
+
* Which axes the current placement allows.
|
|
8473
|
+
*
|
|
8474
|
+
* A full-bleed layout is `100vw`/`100vh` by definition and cannot be resized
|
|
8475
|
+
* at all; a docked panel owns its height, leaving only its inner edge. Read
|
|
8476
|
+
* per interaction, because `placement` is a live attribute.
|
|
8477
|
+
*/
|
|
8478
|
+
#resizeAxis() {
|
|
8479
|
+
switch (this.getAttribute("placement")) {
|
|
8480
|
+
case "full":
|
|
8481
|
+
case "page":
|
|
8482
|
+
return "none";
|
|
8483
|
+
case "sidebar":
|
|
8484
|
+
case "side":
|
|
8485
|
+
return "width";
|
|
8486
|
+
default:
|
|
8487
|
+
return "both";
|
|
8488
|
+
}
|
|
8489
|
+
}
|
|
8490
|
+
/**
|
|
8491
|
+
* Which edges the layout is holding still, by measuring rather than guessing.
|
|
8492
|
+
*
|
|
8493
|
+
* A resize has to be computed from the edge that does not move, and which
|
|
8494
|
+
* edge that is belongs to the **host's layout**, not to `placement`: a
|
|
8495
|
+
* floating panel is pinned bottom-right, while an embedded one goes wherever
|
|
8496
|
+
* the page's own CSS puts it — flex-start, flex-end, a grid cell. Mapping
|
|
8497
|
+
* placement to a corner got this wrong for any host that right-aligns the
|
|
8498
|
+
* element, and the symptom is bad enough to read as a broken control: the
|
|
8499
|
+
* panel shrinks when dragged outward, travelling by its opposite corner.
|
|
8500
|
+
*
|
|
8501
|
+
* So: nudge the size by a pixel, see which edges stayed put, and undo. One
|
|
8502
|
+
* forced reflow per drag, which is cheap next to being wrong.
|
|
8503
|
+
*/
|
|
8504
|
+
#measureAnchor() {
|
|
8505
|
+
const before = this.getBoundingClientRect();
|
|
8506
|
+
const width = this.style.getPropertyValue("--ag-ui-width");
|
|
8507
|
+
const height = this.style.getPropertyValue("--ag-ui-height");
|
|
8508
|
+
this.#applySize({ width: before.width + 1, height: before.height + 1 });
|
|
8509
|
+
const after = this.getBoundingClientRect();
|
|
8510
|
+
this.#restoreProperty("--ag-ui-width", width);
|
|
8511
|
+
this.#restoreProperty("--ag-ui-height", height);
|
|
8512
|
+
return {
|
|
8513
|
+
x: Math.abs(after.left - before.left) < 0.5 ? "left" : "right",
|
|
8514
|
+
y: Math.abs(after.top - before.top) < 0.5 ? "top" : "bottom"
|
|
8515
|
+
};
|
|
8516
|
+
}
|
|
8517
|
+
/** Stamp the measured anchor so the shadow CSS can place the grip. */
|
|
8518
|
+
#syncResizeAnchor() {
|
|
8519
|
+
if (!this.#connected) {
|
|
8520
|
+
return;
|
|
8521
|
+
}
|
|
8522
|
+
const anchor = this.#measureAnchor();
|
|
8523
|
+
this.setAttribute("data-resize-anchor", `${anchor.y}-${anchor.x}`);
|
|
8524
|
+
}
|
|
8525
|
+
/** Put a custom property back to a previous value, or remove it if there was none. */
|
|
8526
|
+
#restoreProperty(name, value) {
|
|
8527
|
+
if (value === "") {
|
|
8528
|
+
this.style.removeProperty(name);
|
|
8529
|
+
return;
|
|
8530
|
+
}
|
|
8531
|
+
this.style.setProperty(name, value);
|
|
8532
|
+
}
|
|
8533
|
+
/**
|
|
8534
|
+
* Write a dragged size onto the host as custom properties.
|
|
8535
|
+
*
|
|
8536
|
+
* Properties rather than inline `width` / `height`: the placement rules set
|
|
8537
|
+
* those same properties, so an inline dimension would outrank them and a
|
|
8538
|
+
* panel dragged while floating would keep that width after switching to
|
|
8539
|
+
* fullscreen.
|
|
8540
|
+
*/
|
|
8541
|
+
#applySize(size) {
|
|
8542
|
+
if (size.width !== void 0) {
|
|
8543
|
+
this.style.setProperty("--ag-ui-width", `${size.width}px`);
|
|
8544
|
+
}
|
|
8545
|
+
if (size.height !== void 0) {
|
|
8546
|
+
this.style.setProperty("--ag-ui-height", `${size.height}px`);
|
|
8547
|
+
}
|
|
8548
|
+
}
|
|
8549
|
+
/** Persist a dragged size per tab, alongside the collapsed/theme preferences. */
|
|
8550
|
+
#persistSize(size) {
|
|
8551
|
+
const stored = { ...this.#readSize(), ...size };
|
|
8552
|
+
sessionStorage.setItem(this.#storageKey(SIZE_KEY), JSON.stringify(stored));
|
|
8553
|
+
}
|
|
8554
|
+
/** The persisted size for this instance, or an empty record. */
|
|
8555
|
+
#readSize() {
|
|
8556
|
+
const raw = this.#readScopedItem(SIZE_KEY);
|
|
8557
|
+
if (raw === null) {
|
|
8558
|
+
return {};
|
|
8559
|
+
}
|
|
8560
|
+
try {
|
|
8561
|
+
const parsed = JSON.parse(raw);
|
|
8562
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
8563
|
+
} catch {
|
|
8564
|
+
return {};
|
|
8565
|
+
}
|
|
8566
|
+
}
|
|
8085
8567
|
/** This instance's namespaced form of an origin-scoped storage key. */
|
|
8086
8568
|
#storageKey(base) {
|
|
8087
8569
|
return this.#storageNs === "" ? base : `${base}:${this.#storageNs}`;
|
|
@@ -8476,9 +8958,32 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8476
8958
|
this.#rail.setAttribute("aria-label", this.#strings.expand);
|
|
8477
8959
|
this.#rail.append(this.#iconElement("launcher", "launcher-icon", "\u{1F4AC}"));
|
|
8478
8960
|
this.#rail.addEventListener("click", () => this.setCollapsed(false));
|
|
8961
|
+
this.#chat.append(
|
|
8962
|
+
createResizeHandle({
|
|
8963
|
+
axis: () => this.#resizeAxis(),
|
|
8964
|
+
anchor: () => this.#measureAnchor(),
|
|
8965
|
+
rect: () => this.getBoundingClientRect(),
|
|
8966
|
+
apply: (size) => this.#applySize(size),
|
|
8967
|
+
commit: (size) => {
|
|
8968
|
+
this.#persistSize(size);
|
|
8969
|
+
this.#syncResizeAnchor();
|
|
8970
|
+
},
|
|
8971
|
+
label: this.#strings.resizePanel
|
|
8972
|
+
})
|
|
8973
|
+
);
|
|
8479
8974
|
this.#root.append(style, this.#chat, this.#rail);
|
|
8480
8975
|
}
|
|
8481
|
-
/**
|
|
8976
|
+
/**
|
|
8977
|
+
* Build a header control button: a named slot a host can project markup into,
|
|
8978
|
+
* with the built-in glyph as the slot's fallback.
|
|
8979
|
+
*
|
|
8980
|
+
* The glyph used to be the button's own `textContent`, which left a host able
|
|
8981
|
+
* to restyle the control through its `part` but unable to replace it — a CSS
|
|
8982
|
+
* `content` override could swap one character for another, and nothing could
|
|
8983
|
+
* supply a brand `<img>` or `<svg>`. This is the same slot-with-fallback
|
|
8984
|
+
* idiom the header icon already uses, so existing embeds render exactly as
|
|
8985
|
+
* before.
|
|
8986
|
+
*/
|
|
8482
8987
|
#headerButton(modifier, label, glyph) {
|
|
8483
8988
|
const button2 = document.createElement("button");
|
|
8484
8989
|
button2.type = "button";
|
|
@@ -8486,7 +8991,10 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8486
8991
|
button2.setAttribute("part", `header-button ${modifier}-button`);
|
|
8487
8992
|
button2.title = label;
|
|
8488
8993
|
button2.setAttribute("aria-label", label);
|
|
8489
|
-
|
|
8994
|
+
const slot = document.createElement("slot");
|
|
8995
|
+
slot.name = `icon-${modifier}`;
|
|
8996
|
+
slot.append(document.createTextNode(glyph));
|
|
8997
|
+
button2.append(slot);
|
|
8490
8998
|
return button2;
|
|
8491
8999
|
}
|
|
8492
9000
|
/**
|
|
@@ -8726,7 +9234,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8726
9234
|
request.message = confirmText;
|
|
8727
9235
|
}
|
|
8728
9236
|
this.#confirmAbort = new AbortController();
|
|
8729
|
-
const decision = requestConfirmation(this.#
|
|
9237
|
+
const decision = requestConfirmation(this.#ensureGroup(), request, {
|
|
8730
9238
|
signal: this.#confirmAbort.signal,
|
|
8731
9239
|
strings: this.#strings
|
|
8732
9240
|
});
|
|
@@ -8734,6 +9242,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8734
9242
|
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
8735
9243
|
const accepted = await decision;
|
|
8736
9244
|
this.#confirmAbort = null;
|
|
9245
|
+
card.recordDecision(accepted ? "approved" : "declined");
|
|
8737
9246
|
if (!accepted) {
|
|
8738
9247
|
const message = this.#strings.declinedAction;
|
|
8739
9248
|
card.settle(TOOL_CALL_STATUS.DECLINED, message);
|
|
@@ -8794,6 +9303,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8794
9303
|
const approved = this.approvalRenderer !== null ? await this.approvalRenderer(request, { signal }) : await requestApproval(this.#ensureGroup(), request, { signal, strings: this.#strings });
|
|
8795
9304
|
this.#updateEmptyState();
|
|
8796
9305
|
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
9306
|
+
card?.recordDecision(approved ? "approved" : "declined");
|
|
8797
9307
|
if (approved) {
|
|
8798
9308
|
responses[interrupt.id] = { status: "resolved", payload: { approved: true } };
|
|
8799
9309
|
} else {
|
|
@@ -8999,7 +9509,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
8999
9509
|
}
|
|
9000
9510
|
const labelled = this.#resolveTool(call.name)?.parameters[X_SUMMARY_KEY];
|
|
9001
9511
|
const summary = typeof labelled === "string" ? labelled : this.toolSummaries[call.name] ?? this.#toolCatalog[call.name] ?? prettifyToolName(call.name);
|
|
9002
|
-
const card = new ToolCallCard(call.name, call.args,
|
|
9512
|
+
const card = new ToolCallCard(call.name, call.args, summary, this.#strings);
|
|
9003
9513
|
this.#toolCards.set(call.id, card);
|
|
9004
9514
|
this.#ensureGroup().appendChild(card.element);
|
|
9005
9515
|
this.#updateEmptyState();
|
|
@@ -9059,7 +9569,7 @@ function setControlValue(el, value) {
|
|
|
9059
9569
|
}
|
|
9060
9570
|
|
|
9061
9571
|
// src/version.ts
|
|
9062
|
-
var VERSION = "0.
|
|
9572
|
+
var VERSION = "0.20.0";
|
|
9063
9573
|
export {
|
|
9064
9574
|
ATTACHMENT_EVENT,
|
|
9065
9575
|
AgUiChat,
|