@artooi/ag-ui-web-component 0.25.2 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -46,12 +46,13 @@ var ICON_FILE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path
46
46
  var ICON_FILE_IMAGE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="4.5" width="17" height="15" rx="2.5"/><circle cx="9" cy="10" r="1.5"/><path d="M4.5 17.5 9 13.5l3.5 3 3-2.5 4.5 4"/></svg>`;
47
47
  var ICON_FILE_PDF = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><rect class="glyph--solid" x="7.5" y="13.5" width="9" height="4.5" rx="1"/></svg>`;
48
48
  var ICON_FILE_TEXT = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><path d="M8.5 13.5h7M8.5 17h4.5"/></svg>`;
49
+ var CHART_ACTIVITY_TYPE = "chart";
49
50
 
50
51
  // src/skills/fill_template.ts
51
52
  var PLACEHOLDER_RE = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
52
53
  function fillTemplate(template, values) {
53
54
  const missing = [];
54
- const text2 = template.replace(PLACEHOLDER_RE, (token, key) => {
55
+ const text3 = template.replace(PLACEHOLDER_RE, (token, key) => {
55
56
  const value = values[key];
56
57
  if (value === void 0 || value === null || value === "") {
57
58
  if (!missing.includes(key)) {
@@ -61,7 +62,7 @@ function fillTemplate(template, values) {
61
62
  }
62
63
  return String(value);
63
64
  });
64
- return { text: text2, missing };
65
+ return { text: text3, missing };
65
66
  }
66
67
 
67
68
  // src/skills/parse_skills.ts
@@ -131,17 +132,17 @@ var setInputValue = prototypeSetter(HTMLInputElement.prototype, "value");
131
132
  var setTextareaValue = prototypeSetter(HTMLTextAreaElement.prototype, "value");
132
133
  var setSelectValue = prototypeSetter(HTMLSelectElement.prototype, "value");
133
134
  var setInputChecked = prototypeSetter(HTMLInputElement.prototype, "checked");
134
- function setNativeValue(el, value) {
135
- if (el instanceof HTMLTextAreaElement) {
136
- setTextareaValue.call(el, value);
137
- } else if (el instanceof HTMLSelectElement) {
138
- setSelectValue.call(el, value);
135
+ function setNativeValue(el2, value) {
136
+ if (el2 instanceof HTMLTextAreaElement) {
137
+ setTextareaValue.call(el2, value);
138
+ } else if (el2 instanceof HTMLSelectElement) {
139
+ setSelectValue.call(el2, value);
139
140
  } else {
140
- setInputValue.call(el, value);
141
+ setInputValue.call(el2, value);
141
142
  }
142
143
  }
143
- function setNativeChecked(el, checked) {
144
- setInputChecked.call(el, checked);
144
+ function setNativeChecked(el2, checked) {
145
+ setInputChecked.call(el2, checked);
145
146
  }
146
147
 
147
148
  // src/dom/animations.ts
@@ -162,42 +163,42 @@ function motionDelay(ms) {
162
163
  }
163
164
  return delay(ms);
164
165
  }
165
- function accentColor(el, fallback) {
166
- const themed = window.getComputedStyle(el).getPropertyValue(ACCENT_PROPERTY).trim();
166
+ function accentColor(el2, fallback) {
167
+ const themed = window.getComputedStyle(el2).getPropertyValue(ACCENT_PROPERTY).trim();
167
168
  return themed === "" ? fallback : themed;
168
169
  }
169
- function ringShadow(el) {
170
- return `0 0 0 3px ${accentColor(el, ACCENT_HALO)}`;
170
+ function ringShadow(el2) {
171
+ return `0 0 0 3px ${accentColor(el2, ACCENT_HALO)}`;
171
172
  }
172
- async function typeInto(el, value, options = {}) {
173
+ async function typeInto(el2, value, options = {}) {
173
174
  const charDelayMs = options.charDelayMs ?? 35;
174
- setNativeValue(el, "");
175
- el.dispatchEvent(new Event("input", { bubbles: true }));
175
+ setNativeValue(el2, "");
176
+ el2.dispatchEvent(new Event("input", { bubbles: true }));
176
177
  for (const char of value) {
177
- setNativeValue(el, el.value + char);
178
- el.dispatchEvent(new Event("input", { bubbles: true }));
178
+ setNativeValue(el2, el2.value + char);
179
+ el2.dispatchEvent(new Event("input", { bubbles: true }));
179
180
  if (charDelayMs > 0) {
180
181
  await delay(charDelayMs);
181
182
  }
182
183
  }
183
- el.dispatchEvent(new Event("change", { bubbles: true }));
184
+ el2.dispatchEvent(new Event("change", { bubbles: true }));
184
185
  }
185
- async function highlightThenClick(el, options = {}) {
186
+ async function highlightThenClick(el2, options = {}) {
186
187
  const highlightMs = options.highlightMs ?? 280;
187
- const previousOutline = el.style.outline;
188
- const previousOffset = el.style.outlineOffset;
189
- el.style.outline = `2px solid ${accentColor(el, ACCENT)}`;
190
- el.style.outlineOffset = "2px";
188
+ const previousOutline = el2.style.outline;
189
+ const previousOffset = el2.style.outlineOffset;
190
+ el2.style.outline = `2px solid ${accentColor(el2, ACCENT)}`;
191
+ el2.style.outlineOffset = "2px";
191
192
  await delay(highlightMs);
192
- el.style.outline = previousOutline;
193
- el.style.outlineOffset = previousOffset;
194
- el.click();
193
+ el2.style.outline = previousOutline;
194
+ el2.style.outlineOffset = previousOffset;
195
+ el2.click();
195
196
  }
196
197
  var SCROLL_SETTLE_MS = 600;
197
198
  var SCROLL_START_MS = 100;
198
- function scrollIntoCenterView(el, options = {}) {
199
+ function scrollIntoCenterView(el2, options = {}) {
199
200
  const reduced = prefersReducedMotion();
200
- el.scrollIntoView({
201
+ el2.scrollIntoView({
201
202
  block: "center",
202
203
  inline: "nearest",
203
204
  behavior: reduced ? "auto" : "smooth"
@@ -225,85 +226,85 @@ function scrollIntoCenterView(el, options = {}) {
225
226
  }
226
227
  var FLASH_MS = 1200;
227
228
  var FLASH_FADE_RATIO = 1 / 3;
228
- async function flashRing(el, options, focusByDefault) {
229
+ async function flashRing(el2, options, focusByDefault) {
229
230
  if (options.focus ?? focusByDefault) {
230
- el.focus({ preventScroll: true });
231
+ el2.focus({ preventScroll: true });
231
232
  }
232
233
  const flashMs = options.flashMs ?? FLASH_MS;
233
234
  if (flashMs <= 0) {
234
235
  return;
235
236
  }
236
- const previousOutline = el.style.outline;
237
- const previousOffset = el.style.outlineOffset;
238
- const previousTransition = el.style.transition;
239
- const color = options.color ?? accentColor(el, ACCENT);
240
- el.style.outline = `3px solid ${color}`;
241
- el.style.outlineOffset = "2px";
237
+ const previousOutline = el2.style.outline;
238
+ const previousOffset = el2.style.outlineOffset;
239
+ const previousTransition = el2.style.transition;
240
+ const color = options.color ?? accentColor(el2, ACCENT);
241
+ el2.style.outline = `3px solid ${color}`;
242
+ el2.style.outlineOffset = "2px";
242
243
  const fadeMs = prefersReducedMotion() ? 0 : Math.round(flashMs * FLASH_FADE_RATIO);
243
244
  await delay(flashMs - fadeMs);
244
245
  if (fadeMs > 0) {
245
- el.style.transition = `outline-color ${fadeMs}ms ease-out`;
246
- el.style.outline = "3px solid transparent";
246
+ el2.style.transition = `outline-color ${fadeMs}ms ease-out`;
247
+ el2.style.outline = "3px solid transparent";
247
248
  await delay(fadeMs);
248
249
  }
249
- el.style.outline = previousOutline;
250
- el.style.outlineOffset = previousOffset;
251
- el.style.transition = previousTransition;
250
+ el2.style.outline = previousOutline;
251
+ el2.style.outlineOffset = previousOffset;
252
+ el2.style.transition = previousTransition;
252
253
  }
253
- function flash(el, options = {}) {
254
- return flashRing(el, options, false);
254
+ function flash(el2, options = {}) {
255
+ return flashRing(el2, options, false);
255
256
  }
256
- function focusWithFlash(el, options = {}) {
257
- return flashRing(el, options, true);
257
+ function focusWithFlash(el2, options = {}) {
258
+ return flashRing(el2, options, true);
258
259
  }
259
- async function pressThenClick(el, options = {}) {
260
+ async function pressThenClick(el2, options = {}) {
260
261
  const pressMs = options.pressMs ?? 140;
261
- const previousTransform = el.style.transform;
262
- const previousTransition = el.style.transition;
263
- const previousShadow = el.style.boxShadow;
264
- el.style.transition = "transform 80ms ease";
265
- el.style.transform = "scale(0.96)";
266
- el.style.boxShadow = ringShadow(el);
262
+ const previousTransform = el2.style.transform;
263
+ const previousTransition = el2.style.transition;
264
+ const previousShadow = el2.style.boxShadow;
265
+ el2.style.transition = "transform 80ms ease";
266
+ el2.style.transform = "scale(0.96)";
267
+ el2.style.boxShadow = ringShadow(el2);
267
268
  await motionDelay(pressMs);
268
- el.style.transform = previousTransform;
269
- el.style.transition = previousTransition;
270
- el.style.boxShadow = previousShadow;
271
- el.click();
269
+ el2.style.transform = previousTransform;
270
+ el2.style.transition = previousTransition;
271
+ el2.style.boxShadow = previousShadow;
272
+ el2.click();
272
273
  }
273
- function findOption(el, value) {
274
- for (const option of Array.from(el.options)) {
274
+ function findOption(el2, value) {
275
+ for (const option of Array.from(el2.options)) {
275
276
  if (option.value === value || option.text === value) {
276
277
  return option;
277
278
  }
278
279
  }
279
280
  return null;
280
281
  }
281
- async function selectOption(el, value, options = {}) {
282
- const option = findOption(el, value);
282
+ async function selectOption(el2, value, options = {}) {
283
+ const option = findOption(el2, value);
283
284
  if (option === null) {
284
285
  throw new Error(`no <option> matching "${value}"`);
285
286
  }
286
287
  const highlightMs = options.highlightMs ?? 220;
287
- const previousOutline = el.style.outline;
288
- const previousOffset = el.style.outlineOffset;
289
- el.style.outline = `2px solid ${accentColor(el, ACCENT)}`;
290
- el.style.outlineOffset = "2px";
288
+ const previousOutline = el2.style.outline;
289
+ const previousOffset = el2.style.outlineOffset;
290
+ el2.style.outline = `2px solid ${accentColor(el2, ACCENT)}`;
291
+ el2.style.outlineOffset = "2px";
291
292
  await motionDelay(highlightMs);
292
- setNativeValue(el, option.value);
293
- el.dispatchEvent(new Event("input", { bubbles: true }));
294
- el.dispatchEvent(new Event("change", { bubbles: true }));
295
- el.style.outline = previousOutline;
296
- el.style.outlineOffset = previousOffset;
293
+ setNativeValue(el2, option.value);
294
+ el2.dispatchEvent(new Event("input", { bubbles: true }));
295
+ el2.dispatchEvent(new Event("change", { bubbles: true }));
296
+ el2.style.outline = previousOutline;
297
+ el2.style.outlineOffset = previousOffset;
297
298
  }
298
- async function toggleControl(el, checked, options = {}) {
299
+ async function toggleControl(el2, checked, options = {}) {
299
300
  const flashMs = options.flashMs ?? 200;
300
- const previousShadow = el.style.boxShadow;
301
- el.style.boxShadow = ringShadow(el);
301
+ const previousShadow = el2.style.boxShadow;
302
+ el2.style.boxShadow = ringShadow(el2);
302
303
  await motionDelay(flashMs);
303
- setNativeChecked(el, checked);
304
- el.dispatchEvent(new Event("input", { bubbles: true }));
305
- el.dispatchEvent(new Event("change", { bubbles: true }));
306
- el.style.boxShadow = previousShadow;
304
+ setNativeChecked(el2, checked);
305
+ el2.dispatchEvent(new Event("input", { bubbles: true }));
306
+ el2.dispatchEvent(new Event("change", { bubbles: true }));
307
+ el2.style.boxShadow = previousShadow;
307
308
  }
308
309
 
309
310
  // src/tools/page_action_tools.ts
@@ -677,7 +678,7 @@ function attachCopyButtons(root, strings) {
677
678
  }
678
679
  }
679
680
  function button(code, strings) {
680
- const text2 = code.textContent;
681
+ const text3 = code.textContent;
681
682
  const copy = document.createElement("button");
682
683
  copy.type = "button";
683
684
  copy.className = "code-copy";
@@ -686,19 +687,19 @@ function button(code, strings) {
686
687
  copy.title = strings.copyCode;
687
688
  copy.setAttribute("aria-label", strings.copyCode);
688
689
  copy.addEventListener("click", () => {
689
- void writeText(text2).then((ok) => {
690
+ void writeText(text3).then((ok) => {
690
691
  confirm(copy, ok ? strings.copied : strings.copyFailed, strings);
691
692
  });
692
693
  });
693
694
  return copy;
694
695
  }
695
- async function writeText(text2) {
696
+ async function writeText(text3) {
696
697
  const clipboard = navigator.clipboard;
697
698
  if (clipboard === void 0) {
698
699
  return false;
699
700
  }
700
701
  try {
701
- await clipboard.writeText(text2);
702
+ await clipboard.writeText(text3);
702
703
  return true;
703
704
  } catch {
704
705
  return false;
@@ -984,6 +985,376 @@ function accepts(accept, file) {
984
985
  });
985
986
  }
986
987
 
988
+ // src/ui/chart_block.ts
989
+ var SVG_NS = "http://www.w3.org/2000/svg";
990
+ var WIDTH = 480;
991
+ var HEIGHT = 220;
992
+ var PAD = { top: 20, right: 12, bottom: 30, left: 44 };
993
+ var PLOT_W = WIDTH - PAD.left - PAD.right;
994
+ var PLOT_H = HEIGHT - PAD.top - PAD.bottom;
995
+ var SERIES_COLORS = [
996
+ "var(--ag-ui-chart-1, #4f7cff)",
997
+ "var(--ag-ui-chart-2, #21b573)",
998
+ "var(--ag-ui-chart-3, #e0803c)",
999
+ "var(--ag-ui-chart-4, #b563d8)",
1000
+ "var(--ag-ui-chart-5, #d84f6e)",
1001
+ "var(--ag-ui-chart-6, #3ba7c4)"
1002
+ ];
1003
+ function seriesColor(index) {
1004
+ return SERIES_COLORS[index % SERIES_COLORS.length];
1005
+ }
1006
+ function el(name, attrs) {
1007
+ const node = document.createElementNS(SVG_NS, name);
1008
+ for (const [key, value] of Object.entries(attrs)) {
1009
+ node.setAttribute(key, String(value));
1010
+ }
1011
+ return node;
1012
+ }
1013
+ function text(value, attrs) {
1014
+ const node = el("text", {
1015
+ "font-size": 10,
1016
+ fill: "currentColor",
1017
+ "fill-opacity": 0.65,
1018
+ ...attrs
1019
+ });
1020
+ node.textContent = value;
1021
+ return node;
1022
+ }
1023
+ function stackRunning(spec) {
1024
+ const seen = [];
1025
+ spec.labels.forEach((_label, i) => {
1026
+ let running = 0;
1027
+ for (const series of spec.series) {
1028
+ running += series.points[i] ?? 0;
1029
+ seen.push(running);
1030
+ }
1031
+ });
1032
+ return seen;
1033
+ }
1034
+ function extent(spec) {
1035
+ const values = spec.kind === "stacked" ? stackRunning(spec) : spec.series.flatMap((series) => [...series.points]);
1036
+ const max = Math.max(0, ...values);
1037
+ const min = Math.min(0, ...values);
1038
+ return max === min ? { min, max: max + 1 } : { min, max };
1039
+ }
1040
+ function scaleY(value, min, max) {
1041
+ return PAD.top + PLOT_H - (value - min) / (max - min) * PLOT_H;
1042
+ }
1043
+ function bandCentre(index, count) {
1044
+ const step = PLOT_W / count;
1045
+ return PAD.left + step * index + step / 2;
1046
+ }
1047
+ function drawAxes(svg2, spec, min, max) {
1048
+ for (const value of [min, max]) {
1049
+ const y2 = scaleY(value, min, max);
1050
+ svg2.appendChild(
1051
+ el("line", {
1052
+ x1: PAD.left,
1053
+ y1: y2,
1054
+ x2: WIDTH - PAD.right,
1055
+ y2,
1056
+ stroke: "currentColor",
1057
+ "stroke-opacity": value === min ? 0.35 : 0.12
1058
+ })
1059
+ );
1060
+ svg2.appendChild(
1061
+ text(String(Math.round(value)), { x: PAD.left - 6, y: y2 + 4, "text-anchor": "end" })
1062
+ );
1063
+ }
1064
+ spec.labels.forEach((label, i) => {
1065
+ svg2.appendChild(
1066
+ text(label, {
1067
+ x: bandCentre(i, spec.labels.length),
1068
+ y: HEIGHT - PAD.bottom + 16,
1069
+ "text-anchor": "middle"
1070
+ })
1071
+ );
1072
+ });
1073
+ }
1074
+ function drawBars(svg2, spec, min, max) {
1075
+ const step = PLOT_W / spec.labels.length;
1076
+ const width = step * 0.7 / spec.series.length;
1077
+ const base = scaleY(min, min, max);
1078
+ spec.series.forEach((series, s) => {
1079
+ series.points.forEach((value, i) => {
1080
+ const y2 = scaleY(value, min, max);
1081
+ svg2.appendChild(
1082
+ el("rect", {
1083
+ x: PAD.left + step * i + step * 0.15 + width * s,
1084
+ y: y2,
1085
+ width,
1086
+ height: Math.max(1, base - y2),
1087
+ fill: seriesColor(s),
1088
+ rx: 2
1089
+ })
1090
+ );
1091
+ });
1092
+ });
1093
+ }
1094
+ function drawStacked(svg2, spec, min, max) {
1095
+ const step = PLOT_W / spec.labels.length;
1096
+ const width = step * 0.7;
1097
+ const running = spec.labels.map(() => 0);
1098
+ spec.series.forEach((series, s) => {
1099
+ series.points.forEach((value, i) => {
1100
+ const from = running[i] ?? 0;
1101
+ const to = from + value;
1102
+ running[i] = to;
1103
+ const y2 = scaleY(to, min, max);
1104
+ svg2.appendChild(
1105
+ el("rect", {
1106
+ x: PAD.left + step * i + step * 0.15,
1107
+ y: y2,
1108
+ width,
1109
+ height: Math.max(1, scaleY(from, min, max) - y2),
1110
+ fill: seriesColor(s)
1111
+ })
1112
+ );
1113
+ });
1114
+ });
1115
+ }
1116
+ function drawLines(svg2, spec, min, max) {
1117
+ spec.series.forEach((series, s) => {
1118
+ const points = series.points.map((value, i) => `${bandCentre(i, spec.labels.length)},${scaleY(value, min, max)}`).join(" ");
1119
+ svg2.appendChild(
1120
+ el("polyline", {
1121
+ points,
1122
+ fill: "none",
1123
+ stroke: seriesColor(s),
1124
+ "stroke-width": 2,
1125
+ "stroke-linejoin": "round"
1126
+ })
1127
+ );
1128
+ });
1129
+ }
1130
+ function drawScatter(svg2, spec, min, max) {
1131
+ spec.series.forEach((series, s) => {
1132
+ series.points.forEach((value, i) => {
1133
+ svg2.appendChild(
1134
+ el("circle", {
1135
+ cx: bandCentre(i, spec.labels.length),
1136
+ cy: scaleY(value, min, max),
1137
+ r: 4,
1138
+ fill: seriesColor(s),
1139
+ "fill-opacity": 0.85
1140
+ })
1141
+ );
1142
+ });
1143
+ });
1144
+ }
1145
+ function drawPie(svg2, points) {
1146
+ const total = points.reduce((sum, value) => sum + value, 0);
1147
+ const cx = WIDTH / 2;
1148
+ const cy = PAD.top + PLOT_H / 2;
1149
+ const r = Math.min(PLOT_W, PLOT_H) / 2;
1150
+ if (total === 0) {
1151
+ svg2.appendChild(
1152
+ el("circle", { cx, cy, r, fill: "none", stroke: "currentColor", "stroke-opacity": 0.3 })
1153
+ );
1154
+ return;
1155
+ }
1156
+ let angle = -Math.PI / 2;
1157
+ points.forEach((value, i) => {
1158
+ const sweep = value / total * Math.PI * 2;
1159
+ const end = angle + sweep;
1160
+ if (sweep >= Math.PI * 2) {
1161
+ svg2.appendChild(el("circle", { cx, cy, r, fill: seriesColor(i) }));
1162
+ } else {
1163
+ const x1 = cx + r * Math.cos(angle);
1164
+ const y1 = cy + r * Math.sin(angle);
1165
+ const x2 = cx + r * Math.cos(end);
1166
+ const y2 = cy + r * Math.sin(end);
1167
+ const large = sweep > Math.PI ? 1 : 0;
1168
+ svg2.appendChild(
1169
+ el("path", {
1170
+ d: `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`,
1171
+ fill: seriesColor(i)
1172
+ })
1173
+ );
1174
+ }
1175
+ angle = end;
1176
+ });
1177
+ }
1178
+ function buildLegend(entries2) {
1179
+ if (entries2.length < 2) {
1180
+ return null;
1181
+ }
1182
+ const row = document.createElement("div");
1183
+ row.className = "chart-legend";
1184
+ row.setAttribute("part", "chart-legend");
1185
+ entries2.forEach((label, i) => {
1186
+ const item = document.createElement("span");
1187
+ item.className = "chart-legend-item";
1188
+ const swatch = document.createElement("span");
1189
+ swatch.className = "chart-legend-swatch";
1190
+ swatch.style.background = seriesColor(i);
1191
+ item.append(swatch, document.createTextNode(label));
1192
+ row.appendChild(item);
1193
+ });
1194
+ return row;
1195
+ }
1196
+ function renderChart(spec) {
1197
+ if (spec.labels.length === 0 || spec.series.length === 0) {
1198
+ return null;
1199
+ }
1200
+ const block = document.createElement("div");
1201
+ block.className = "chart-block";
1202
+ block.setAttribute("part", "chart-block");
1203
+ if (spec.title !== void 0 && spec.title !== "") {
1204
+ const heading = document.createElement("div");
1205
+ heading.className = "chart-title";
1206
+ heading.setAttribute("part", "chart-title");
1207
+ heading.textContent = spec.title;
1208
+ block.appendChild(heading);
1209
+ }
1210
+ const svg2 = el("svg", { viewBox: `0 0 ${WIDTH} ${HEIGHT}`, width: "100%", role: "img" });
1211
+ svg2.setAttribute("aria-label", spec.title ?? `${spec.kind} chart`);
1212
+ if (spec.kind === "pie") {
1213
+ const first = spec.series[0];
1214
+ drawPie(
1215
+ svg2,
1216
+ first.points.map((value) => Math.max(0, value))
1217
+ );
1218
+ } else {
1219
+ const { min, max } = extent(spec);
1220
+ drawAxes(svg2, spec, min, max);
1221
+ if (spec.kind === "bar") {
1222
+ drawBars(svg2, spec, min, max);
1223
+ } else if (spec.kind === "stacked") {
1224
+ drawStacked(svg2, spec, min, max);
1225
+ } else if (spec.kind === "line") {
1226
+ drawLines(svg2, spec, min, max);
1227
+ } else {
1228
+ drawScatter(svg2, spec, min, max);
1229
+ }
1230
+ }
1231
+ block.appendChild(svg2);
1232
+ const legend = buildLegend(
1233
+ spec.kind === "pie" ? spec.labels : spec.series.map((series) => series.label)
1234
+ );
1235
+ if (legend !== null) {
1236
+ block.appendChild(legend);
1237
+ }
1238
+ return block;
1239
+ }
1240
+
1241
+ // src/ui/chart_spec_from.ts
1242
+ var KINDS = ["bar", "line", "pie", "scatter", "stacked"];
1243
+ var MAX_POINTS = 2e4;
1244
+ var MAX_LABELS = 2e3;
1245
+ var MAX_MAGNITUDE = 1e15;
1246
+ function asKind(value) {
1247
+ return KINDS.includes(value) ? value : "bar";
1248
+ }
1249
+ function asNumbers(value) {
1250
+ if (!Array.isArray(value)) {
1251
+ return null;
1252
+ }
1253
+ const out = [];
1254
+ for (const item of value) {
1255
+ if (typeof item !== "number" || !Number.isFinite(item)) {
1256
+ return null;
1257
+ }
1258
+ if (Math.abs(item) > MAX_MAGNITUDE) {
1259
+ return null;
1260
+ }
1261
+ out.push(item);
1262
+ }
1263
+ return out;
1264
+ }
1265
+ function asStrings(value) {
1266
+ if (!Array.isArray(value)) {
1267
+ return null;
1268
+ }
1269
+ for (let i = 0; i < value.length; i += 1) {
1270
+ if (typeof value[i] !== "string") {
1271
+ return null;
1272
+ }
1273
+ }
1274
+ return value;
1275
+ }
1276
+ function chartSpecFrom(value) {
1277
+ if (typeof value !== "object" || value === null) {
1278
+ return null;
1279
+ }
1280
+ const raw = value;
1281
+ const labels = asStrings(raw["labels"]);
1282
+ if (labels === null || !Array.isArray(raw["series"])) {
1283
+ return null;
1284
+ }
1285
+ const series = [];
1286
+ for (const entry of raw["series"]) {
1287
+ if (typeof entry !== "object" || entry === null) {
1288
+ return null;
1289
+ }
1290
+ const item = entry;
1291
+ const points = asNumbers(item["points"]);
1292
+ if (points === null || points.length !== labels.length) {
1293
+ return null;
1294
+ }
1295
+ series.push({ label: typeof item["label"] === "string" ? item["label"] : "", points });
1296
+ }
1297
+ if (series.length === 0) {
1298
+ return null;
1299
+ }
1300
+ if (series.length * labels.length > MAX_POINTS || labels.length > MAX_LABELS) {
1301
+ return null;
1302
+ }
1303
+ const kind = asKind(raw["kind"]);
1304
+ const title = raw["title"];
1305
+ return typeof title === "string" ? { kind, title, labels, series } : { kind, labels, series };
1306
+ }
1307
+
1308
+ // src/ui/chart_tool.ts
1309
+ var CHART_TOOL_NAME = "render_chart";
1310
+ function draw(args) {
1311
+ const spec = chartSpecFrom(args);
1312
+ return spec === null ? null : renderChart(spec);
1313
+ }
1314
+ function drawable(args) {
1315
+ const spec = chartSpecFrom(args);
1316
+ return spec !== null && spec.labels.length > 0 && spec.series.length > 0;
1317
+ }
1318
+ var REJECTED = "chart not rendered: expected labels (strings) and series, each with one finite number per label";
1319
+ function createChartTool() {
1320
+ return {
1321
+ name: CHART_TOOL_NAME,
1322
+ description: "Show a chart in the conversation. Supply the data and the page draws it. Every series must have exactly one point per label.",
1323
+ parameters: {
1324
+ type: "object",
1325
+ properties: {
1326
+ kind: { type: "string", enum: ["bar", "line", "pie", "scatter", "stacked"] },
1327
+ title: { type: "string" },
1328
+ labels: { type: "array", items: { type: "string" } },
1329
+ series: {
1330
+ type: "array",
1331
+ items: {
1332
+ type: "object",
1333
+ properties: {
1334
+ label: { type: "string" },
1335
+ points: { type: "array", items: { type: "number" } }
1336
+ },
1337
+ required: ["points"]
1338
+ }
1339
+ }
1340
+ },
1341
+ required: ["labels", "series"],
1342
+ "x-summary": "Draw a chart"
1343
+ },
1344
+ // Says what happened and nothing else; the drawing is `render`'s job. Told
1345
+ // plainly when the arguments are unusable, because the model can fix that
1346
+ // and retry — a silent no-op would leave it believing the chart is on screen.
1347
+ // Answers on what will actually be drawn, not on what validated: a spec can
1348
+ // pass validation and still have nothing to show, and reporting success
1349
+ // then would leave the model believing a chart is on screen. Asks the
1350
+ // question without building the chart, because `render` is about to build
1351
+ // the same one a moment later and drawing it twice is pure waste on a spec
1352
+ // large enough to matter.
1353
+ handler: (args) => drawable(args) ? "chart rendered" : REJECTED,
1354
+ render: draw
1355
+ };
1356
+ }
1357
+
987
1358
  // src/ui/relative_time.ts
988
1359
  function relativeTime(timestamp, now = Date.now(), strings = DEFAULT_UI_STRINGS) {
989
1360
  if (!Number.isFinite(timestamp)) {
@@ -1084,7 +1455,7 @@ var CheckpointMenu = class {
1084
1455
  /** The panel's tabbable controls, in document order. */
1085
1456
  #focusables() {
1086
1457
  return Array.from(this.element.querySelectorAll("button, [tabindex]")).filter(
1087
- (el) => !el.hidden
1458
+ (el2) => !el2.hidden
1088
1459
  );
1089
1460
  }
1090
1461
  /** Escape closes; Tab is trapped so focus cannot wander behind the dialog. */
@@ -1308,10 +1679,10 @@ function requestQuestion(host, request, options = {}) {
1308
1679
  radio.name = group;
1309
1680
  radio.value = choice;
1310
1681
  radio.setAttribute("part", "question-radio");
1311
- const text2 = document.createElement("span");
1312
- text2.setAttribute("part", "question-choice-text");
1313
- text2.textContent = choice;
1314
- label.append(radio, text2);
1682
+ const text3 = document.createElement("span");
1683
+ text3.setAttribute("part", "question-choice-text");
1684
+ text3.textContent = choice;
1685
+ label.append(radio, text3);
1315
1686
  form.appendChild(label);
1316
1687
  radios.push(radio);
1317
1688
  }
@@ -1328,10 +1699,10 @@ function requestQuestion(host, request, options = {}) {
1328
1699
  otherRadio.name = group;
1329
1700
  otherRadio.value = "";
1330
1701
  otherRadio.setAttribute("part", "question-radio");
1331
- const text2 = document.createElement("span");
1332
- text2.setAttribute("part", "question-choice-text");
1333
- text2.textContent = strings.otherOption;
1334
- label.append(otherRadio, text2);
1702
+ const text3 = document.createElement("span");
1703
+ text3.setAttribute("part", "question-choice-text");
1704
+ text3.textContent = strings.otherOption;
1705
+ label.append(otherRadio, text3);
1335
1706
  form.appendChild(label);
1336
1707
  input.disabled = true;
1337
1708
  }
@@ -1652,7 +2023,7 @@ var svgFilters = freeze(["feBlend", "feColorMatrix", "feComponentTransfer", "feC
1652
2023
  var svgDisallowed = freeze(["animate", "color-profile", "cursor", "discard", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignobject", "hatch", "hatchpath", "mesh", "meshgradient", "meshpatch", "meshrow", "missing-glyph", "script", "set", "solidcolor", "unknown", "use"]);
1653
2024
  var mathMl$1 = freeze(["math", "menclose", "merror", "mfenced", "mfrac", "mglyph", "mi", "mlabeledtr", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mphantom", "mroot", "mrow", "ms", "mspace", "msqrt", "mstyle", "msub", "msup", "msubsup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "mprescripts"]);
1654
2025
  var mathMlDisallowed = freeze(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]);
1655
- var text = freeze(["#text"]);
2026
+ var text2 = freeze(["#text"]);
1656
2027
  var html = freeze(["accept", "action", "align", "alt", "autocapitalize", "autocomplete", "autopictureinpicture", "autoplay", "background", "bgcolor", "border", "capture", "cellpadding", "cellspacing", "checked", "cite", "class", "clear", "color", "cols", "colspan", "command", "commandfor", "controls", "controlslist", "coords", "crossorigin", "datetime", "decoding", "default", "dir", "disabled", "disablepictureinpicture", "disableremoteplayback", "download", "draggable", "enctype", "enterkeyhint", "exportparts", "face", "for", "headers", "height", "hidden", "high", "href", "hreflang", "id", "inert", "inputmode", "integrity", "ismap", "kind", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "minlength", "multiple", "muted", "name", "nonce", "noshade", "novalidate", "nowrap", "open", "optimum", "part", "pattern", "placeholder", "playsinline", "popover", "popovertarget", "popovertargetaction", "poster", "preload", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "spellcheck", "scope", "selected", "shape", "size", "sizes", "slot", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "title", "translate", "type", "usemap", "valign", "value", "width", "wrap", "xmlns"]);
1657
2028
  var svg = freeze(["accent-height", "accumulate", "additive", "alignment-baseline", "amplitude", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clippathunits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dominant-baseline", "dur", "edgemode", "elevation", "end", "exponent", "fill", "fill-opacity", "fill-rule", "filter", "filterunits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "mask-type", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "primitiveunits", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "slope", "specularconstant", "specularexponent", "spreadmethod", "startoffset", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "systemlanguage", "tabindex", "tablevalues", "targetx", "targety", "transform", "transform-origin", "text-anchor", "text-decoration", "text-orientation", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xmlns", "y", "y1", "y2", "z", "zoomandpan"]);
1658
2029
  var mathMl = freeze(["accent", "accentunder", "align", "bevelled", "close", "columnalign", "columnlines", "columnspacing", "columnspan", "denomalign", "depth", "dir", "display", "displaystyle", "encoding", "fence", "frame", "height", "href", "id", "largeop", "length", "linethickness", "lquote", "lspace", "mathbackground", "mathcolor", "mathsize", "mathvariant", "maxsize", "minsize", "movablelimits", "notation", "numalign", "open", "rowalign", "rowlines", "rowspacing", "rowspan", "rspace", "rquote", "scriptlevel", "scriptminsize", "scriptsizemultiplier", "selection", "separator", "separators", "stretchy", "subscriptshift", "supscriptshift", "symmetric", "voffset", "width", "xmlns"]);
@@ -1813,7 +2184,7 @@ function createDOMPurify() {
1813
2184
  const MUSTACHE_EXPR$1 = MUSTACHE_EXPR, ERB_EXPR$1 = ERB_EXPR, TMPLIT_EXPR$1 = TMPLIT_EXPR, DATA_ATTR$1 = DATA_ATTR, ARIA_ATTR$1 = ARIA_ATTR, IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA, ATTR_WHITESPACE$1 = ATTR_WHITESPACE, CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
1814
2185
  let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
1815
2186
  let ALLOWED_TAGS2 = null;
1816
- const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
2187
+ const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text2]);
1817
2188
  let ALLOWED_ATTR2 = null;
1818
2189
  const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
1819
2190
  let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
@@ -2012,7 +2383,7 @@ function createDOMPurify() {
2012
2383
  RETURN_DOM = true;
2013
2384
  }
2014
2385
  if (USE_PROFILES) {
2015
- ALLOWED_TAGS2 = addToSet({}, text);
2386
+ ALLOWED_TAGS2 = addToSet({}, text2);
2016
2387
  ALLOWED_ATTR2 = create(null);
2017
2388
  if (USE_PROFILES.html === true) {
2018
2389
  addToSet(ALLOWED_TAGS2, html$1);
@@ -4228,9 +4599,9 @@ var ALLOWED_TAGS = [
4228
4599
  var ALLOWED_ATTR = ["href", "title", "class"];
4229
4600
  var ALLOWED_TAGS_WITH_IMAGES = [...ALLOWED_TAGS, "img"];
4230
4601
  var ALLOWED_ATTR_WITH_IMAGES = [...ALLOWED_ATTR, "src", "alt", "width", "height"];
4231
- function renderMarkdown(text2, options) {
4602
+ function renderMarkdown(text3, options) {
4232
4603
  const allowImages = options?.allowImages === true;
4233
- const rendered = parser.parse(text2, { async: false });
4604
+ const rendered = parser.parse(text3, { async: false });
4234
4605
  const clean = purify.sanitize(rendered, {
4235
4606
  ALLOWED_TAGS: allowImages ? ALLOWED_TAGS_WITH_IMAGES : ALLOWED_TAGS,
4236
4607
  ALLOWED_ATTR: allowImages ? ALLOWED_ATTR_WITH_IMAGES : ALLOWED_ATTR
@@ -4350,7 +4721,7 @@ function wrapWords(root) {
4350
4721
  }
4351
4722
 
4352
4723
  // src/ui/run_notice.ts
4353
- function renderRunNotice(icon, text2, kind) {
4724
+ function renderRunNotice(icon, text3, kind) {
4354
4725
  const notice = document.createElement("div");
4355
4726
  notice.className = `run-notice run-notice--${kind}`;
4356
4727
  notice.setAttribute("part", `run-notice run-notice-${kind}`);
@@ -4363,7 +4734,7 @@ function renderRunNotice(icon, text2, kind) {
4363
4734
  const label = document.createElement("span");
4364
4735
  label.className = "run-notice-text";
4365
4736
  label.setAttribute("part", "run-notice-text");
4366
- label.textContent = text2;
4737
+ label.textContent = text3;
4367
4738
  notice.append(glyph, label);
4368
4739
  return notice;
4369
4740
  }
@@ -6048,6 +6419,50 @@ var STYLES = `
6048
6419
  margin-top: 6px;
6049
6420
  }
6050
6421
 
6422
+ /* Charts.
6423
+ *
6424
+ * The SVG scales to the column and carries no colours of its own beyond the
6425
+ * series palette, so a host restyles it the same way it restyles everything
6426
+ * else. Series colours are custom properties with fallbacks rather than fixed
6427
+ * values, and the axis furniture inherits currentColor at low opacity so it
6428
+ * reads correctly in either theme without a second palette.
6429
+ */
6430
+ .chart-block {
6431
+ align-self: stretch;
6432
+ max-width: 100%;
6433
+ margin: 6px 0;
6434
+ color: var(--_fg);
6435
+ }
6436
+
6437
+ .chart-title {
6438
+ margin-bottom: 2px;
6439
+ font-size: 0.85em;
6440
+ font-weight: 600;
6441
+ opacity: 0.85;
6442
+ }
6443
+
6444
+ .chart-legend {
6445
+ display: flex;
6446
+ flex-wrap: wrap;
6447
+ gap: 4px 12px;
6448
+ margin-top: 4px;
6449
+ font-size: 0.78em;
6450
+ opacity: 0.75;
6451
+ }
6452
+
6453
+ .chart-legend-item {
6454
+ display: inline-flex;
6455
+ align-items: center;
6456
+ gap: 5px;
6457
+ }
6458
+
6459
+ .chart-legend-swatch {
6460
+ width: 9px;
6461
+ height: 9px;
6462
+ border-radius: 2px;
6463
+ flex: 0 0 auto;
6464
+ }
6465
+
6051
6466
  .run-notice {
6052
6467
  display: inline-flex;
6053
6468
  align-items: center;
@@ -6969,7 +7384,7 @@ var ThreadDrawer = class {
6969
7384
  }
6970
7385
  const focusables = Array.from(
6971
7386
  this.#panel.querySelectorAll("button, input, [tabindex]")
6972
- ).filter((el) => !el.hidden);
7387
+ ).filter((el2) => !el2.hidden);
6973
7388
  const first = focusables[0];
6974
7389
  const last = focusables[focusables.length - 1];
6975
7390
  const active = this.#activeElement();
@@ -7137,11 +7552,11 @@ function resultLabels(strings) {
7137
7552
  [TOOL_CALL_STATUS.DECLINED]: strings.declinedLabel
7138
7553
  };
7139
7554
  }
7140
- function formatPayload(text2) {
7555
+ function formatPayload(text3) {
7141
7556
  try {
7142
- return JSON.stringify(JSON.parse(text2), null, 2);
7557
+ return JSON.stringify(JSON.parse(text3), null, 2);
7143
7558
  } catch {
7144
- return text2;
7559
+ return text3;
7145
7560
  }
7146
7561
  }
7147
7562
  var ToolCallCard = class {
@@ -7253,7 +7668,7 @@ var ToolCallCard = class {
7253
7668
  * Flip the status pill to `status` and fill in the result region, whose
7254
7669
  * heading names the outcome (result / error / declined).
7255
7670
  */
7256
- settle(status, text2) {
7671
+ settle(status, text3) {
7257
7672
  if (this.#settled) {
7258
7673
  return;
7259
7674
  }
@@ -7261,7 +7676,7 @@ var ToolCallCard = class {
7261
7676
  this.element.setAttribute("data-status", status);
7262
7677
  this.#status.textContent = statusLabels(this.#strings)[status];
7263
7678
  this.#resultLabel.textContent = resultLabels(this.#strings)[status];
7264
- this.#resultBody.textContent = formatPayload(text2);
7679
+ this.#resultBody.textContent = formatPayload(text3);
7265
7680
  this.#resultSection.hidden = false;
7266
7681
  }
7267
7682
  /** Build one labelled region of the body: a heading plus a payload block. */
@@ -7374,10 +7789,10 @@ var VoiceInput = class {
7374
7789
  this.#setState("transcribing");
7375
7790
  const audio = new Blob(this.#chunks, { type: mimeType || "audio/webm" });
7376
7791
  try {
7377
- const text2 = await this.#transcribe(audio);
7792
+ const text3 = await this.#transcribe(audio);
7378
7793
  this.#setState("idle");
7379
- if (text2 !== "") {
7380
- this.#onText(text2);
7794
+ if (text3 !== "") {
7795
+ this.#onText(text3);
7381
7796
  }
7382
7797
  } catch (error) {
7383
7798
  this.#fail(error instanceof Error ? error.message : this.#strings.transcriptionFailed);
@@ -7614,6 +8029,7 @@ var AgUiClient = class {
7614
8029
  #buildSubscriber(pending, runState) {
7615
8030
  const h = this.#handlers;
7616
8031
  const closed = this.#closedMessageIds;
8032
+ const pendingDeltas = /* @__PURE__ */ new Set();
7617
8033
  return {
7618
8034
  onRunInitialized() {
7619
8035
  h.onRunStart();
@@ -7644,8 +8060,39 @@ var AgUiClient = class {
7644
8060
  onToolCallResultEvent({ event }) {
7645
8061
  h.onToolResult(event.toolCallId, event.content);
7646
8062
  },
7647
- onActivitySnapshotEvent({ event }) {
7648
- h.onActivity(event.activityType, event.content);
8063
+ onActivitySnapshotEvent({ event, messages }) {
8064
+ const known = messages.some(
8065
+ (message) => message.id === event.messageId && message.role === "activity"
8066
+ );
8067
+ if (known) {
8068
+ h.onActivityChanged(event.messageId, event.activityType, event.content);
8069
+ return;
8070
+ }
8071
+ h.onActivity(event.activityType, event.content, event.messageId);
8072
+ },
8073
+ // Deliberately does *not* read the message here. `@ag-ui/client`
8074
+ // dispatches this subscriber **before** applying the patch, so the
8075
+ // message still holds its previous content: redrawing from it would leave
8076
+ // the chart one revision behind for the life of the run, and disagreeing
8077
+ // with what a reload shows. Note which chart moved and read the result on
8078
+ // the change that follows.
8079
+ onActivityDeltaEvent({ event }) {
8080
+ pendingDeltas.add(event.messageId);
8081
+ },
8082
+ // Emitted after the client has written the patched messages, which is the
8083
+ // first moment the result exists. Only the ids marked above are looked at,
8084
+ // so an ordinary text delta does not walk the transcript.
8085
+ onMessagesChanged({ messages }) {
8086
+ if (pendingDeltas.size === 0) {
8087
+ return;
8088
+ }
8089
+ for (const id of pendingDeltas) {
8090
+ const message = messages.find((entry) => entry.id === id);
8091
+ if (message !== void 0 && message.role === "activity") {
8092
+ h.onActivityChanged(id, message.activityType, message.content);
8093
+ }
8094
+ }
8095
+ pendingDeltas.clear();
7649
8096
  },
7650
8097
  // `@ag-ui/client` maps the deprecated THINKING_* events onto these
7651
8098
  // REASONING_* callbacks, so the reasoning family alone covers both
@@ -7857,9 +8304,9 @@ function isOwnedSuffix(suffix) {
7857
8304
  function deriveTitle(messages) {
7858
8305
  for (const message of messages) {
7859
8306
  if (message.role === "user") {
7860
- const text2 = cleanText(message.content);
7861
- if (text2 !== "") {
7862
- return truncate(text2, TITLE_LIMIT);
8307
+ const text3 = cleanText(message.content);
8308
+ if (text3 !== "") {
8309
+ return truncate(text3, TITLE_LIMIT);
7863
8310
  }
7864
8311
  }
7865
8312
  }
@@ -7867,9 +8314,9 @@ function deriveTitle(messages) {
7867
8314
  }
7868
8315
  function derivePreview(messages) {
7869
8316
  for (const message of [...messages].reverse()) {
7870
- const text2 = cleanText(message.content);
7871
- if (text2 !== "") {
7872
- return truncate(text2, PREVIEW_LIMIT);
8317
+ const text3 = cleanText(message.content);
8318
+ if (text3 !== "") {
8319
+ return truncate(text3, PREVIEW_LIMIT);
7873
8320
  }
7874
8321
  }
7875
8322
  return "";
@@ -7877,8 +8324,8 @@ function derivePreview(messages) {
7877
8324
  function cleanText(content) {
7878
8325
  return typeof content === "string" ? content.replace(/\s+/g, " ").trim() : "";
7879
8326
  }
7880
- function truncate(text2, limit) {
7881
- return text2.length <= limit ? text2 : `${text2.slice(0, limit - 1).trimEnd()}\u2026`;
8327
+ function truncate(text3, limit) {
8328
+ return text3.length <= limit ? text3 : `${text3.slice(0, limit - 1).trimEnd()}\u2026`;
7882
8329
  }
7883
8330
 
7884
8331
  // src/core/create_http_agent.ts
@@ -8397,6 +8844,12 @@ var AgUiChat = class extends HTMLElement {
8397
8844
  * (`TOOL_CALL_RESULT`), so the post-run executeTool sweep doesn't overwrite
8398
8845
  * the real output with the generic "executed on the server" fallback.
8399
8846
  */
8847
+ /** Whether a server-pushed chart activity is drawn. Off unless asked for. */
8848
+ #chartActivity = false;
8849
+ /** Card elements by call id, so a rendering handler can find its own card. */
8850
+ #cardElements = /* @__PURE__ */ new Map();
8851
+ /** Chart blocks by activity message id, so an update redraws in place. */
8852
+ #activityBlocks = /* @__PURE__ */ new Map();
8400
8853
  #serverSettled = /* @__PURE__ */ new Set();
8401
8854
  /**
8402
8855
  * Tool calls made during the current interaction, in the order they started,
@@ -9042,7 +9495,7 @@ var AgUiChat = class extends HTMLElement {
9042
9495
  }
9043
9496
  this.#voice = new VoiceInput({
9044
9497
  transcribe,
9045
- onText: (text2) => this.#insertVoiceText(text2),
9498
+ onText: (text3) => this.#insertVoiceText(text3),
9046
9499
  strings: this.#strings
9047
9500
  });
9048
9501
  this.#voiceSlot.appendChild(this.#voice.element);
@@ -9059,9 +9512,9 @@ var AgUiChat = class extends HTMLElement {
9059
9512
  });
9060
9513
  }
9061
9514
  /** Drop a voice transcript into the composer (appended to any typed text). */
9062
- #insertVoiceText(text2) {
9515
+ #insertVoiceText(text3) {
9063
9516
  const current = this.#input.value.trim();
9064
- this.#input.value = current === "" ? text2 : `${current} ${text2}`;
9517
+ this.#input.value = current === "" ? text3 : `${current} ${text3}`;
9065
9518
  this.#onInput();
9066
9519
  this.#input.focus();
9067
9520
  }
@@ -9205,18 +9658,18 @@ var AgUiChat = class extends HTMLElement {
9205
9658
  void this.sendMessage(`/${skill.name}`);
9206
9659
  return;
9207
9660
  }
9208
- const { text: text2, missing } = fillTemplate(skill.prompt, this.skillContext());
9661
+ const { text: text3, missing } = fillTemplate(skill.prompt, this.skillContext());
9209
9662
  if (missing.length > 0) {
9210
9663
  this.#skillHint.textContent = this.#strings.skillNeeds.replace("{title}", skill.title).replace("{fields}", missing.join(", "));
9211
9664
  this.#skillHint.hidden = false;
9212
- this.#input.value = text2;
9665
+ this.#input.value = text3;
9213
9666
  this.#autoGrow();
9214
9667
  this.#input.focus();
9215
- this.#selectFirstPlaceholder(text2);
9668
+ this.#selectFirstPlaceholder(text3);
9216
9669
  return;
9217
9670
  }
9218
9671
  this.#skillHint.hidden = true;
9219
- this.#input.value = text2;
9672
+ this.#input.value = text3;
9220
9673
  this.#autoGrow();
9221
9674
  if (skill.sendImmediately === false) {
9222
9675
  this.#input.focus();
@@ -9230,9 +9683,9 @@ var AgUiChat = class extends HTMLElement {
9230
9683
  * Typing then replaces it, which is the shortest path from "this skill needs
9231
9684
  * a topic" to a sendable prompt.
9232
9685
  */
9233
- #selectFirstPlaceholder(text2) {
9234
- const start = text2.indexOf("{");
9235
- this.#input.setSelectionRange(start, text2.indexOf("}", start) + 1);
9686
+ #selectFirstPlaceholder(text3) {
9687
+ const start = text3.indexOf("{");
9688
+ this.#input.setSelectionRange(start, text3.indexOf("}", start) + 1);
9236
9689
  }
9237
9690
  /** Whether the widget is collapsed (reflected as the `collapsed` attribute). */
9238
9691
  get collapsed() {
@@ -9488,6 +9941,8 @@ var AgUiChat = class extends HTMLElement {
9488
9941
  this.#hidePending();
9489
9942
  this.#toolCards.clear();
9490
9943
  this.#serverSettled.clear();
9944
+ this.#cardElements.clear();
9945
+ this.#activityBlocks.clear();
9491
9946
  this.#initialMessages = [];
9492
9947
  this.#attachTray?.clear();
9493
9948
  this.#messages.replaceChildren(this.#emptyWrap);
@@ -9599,11 +10054,11 @@ var AgUiChat = class extends HTMLElement {
9599
10054
  * the full transcript — tool calls and their results — not just the prose.
9600
10055
  */
9601
10056
  #renderHistoricMessage(message) {
9602
- const text2 = typeof message.content === "string" ? message.content : "";
10057
+ const text3 = typeof message.content === "string" ? message.content : "";
9603
10058
  if (message.role === MESSAGE_ROLE.USER) {
9604
10059
  const attachments = messageAttachments(message);
9605
- if (text2 !== "" || attachments.length > 0) {
9606
- const bubble = this.appendMessage(MESSAGE_ROLE.USER, text2);
10060
+ if (text3 !== "" || attachments.length > 0) {
10061
+ const bubble = this.appendMessage(MESSAGE_ROLE.USER, text3);
9607
10062
  if (attachments.length > 0) {
9608
10063
  bubble.appendChild(renderAttachmentChips(attachments));
9609
10064
  }
@@ -9611,8 +10066,8 @@ var AgUiChat = class extends HTMLElement {
9611
10066
  return;
9612
10067
  }
9613
10068
  if (message.role === MESSAGE_ROLE.ASSISTANT) {
9614
- if (text2 !== "") {
9615
- this.appendMessage(MESSAGE_ROLE.ASSISTANT, text2).classList.add("message--restored");
10069
+ if (text3 !== "") {
10070
+ this.appendMessage(MESSAGE_ROLE.ASSISTANT, text3).classList.add("message--restored");
9616
10071
  }
9617
10072
  for (const call of restoredToolCalls(message.toolCalls)) {
9618
10073
  const restored = {
@@ -9623,7 +10078,18 @@ var AgUiChat = class extends HTMLElement {
9623
10078
  if (this.#noticeIfSkillLoad(restored)) {
9624
10079
  continue;
9625
10080
  }
9626
- this.#cardFor(restored);
10081
+ this.#cardElements.set(restored.id, this.#cardFor(restored).element);
10082
+ const render = this.#resolveTool(restored.name)?.render;
10083
+ if (render !== void 0) {
10084
+ this.#renderToolOutput(render, restored);
10085
+ }
10086
+ }
10087
+ return;
10088
+ }
10089
+ if (message.role === "activity") {
10090
+ const activity = message;
10091
+ if (activity.activityType === CHART_ACTIVITY_TYPE && this.#chartActivity) {
10092
+ this.#drawActivityChart(message.id, activity.content);
9627
10093
  }
9628
10094
  return;
9629
10095
  }
@@ -10196,6 +10662,7 @@ var AgUiChat = class extends HTMLElement {
10196
10662
  }
10197
10663
  const card = this.#cardFor(call);
10198
10664
  this.#toolCards.delete(call.id);
10665
+ this.#cardElements.set(call.id, card.element);
10199
10666
  const tool = this.#resolveTool(call.name);
10200
10667
  if (tool === null) {
10201
10668
  if (!this.#serverSettled.has(call.id)) {
@@ -10237,7 +10704,10 @@ var AgUiChat = class extends HTMLElement {
10237
10704
  this.conversationStore.saveCheckpoint(this.#threadId, { toolCallId: call.id });
10238
10705
  }
10239
10706
  try {
10240
- const result = await tool.handler(call.args);
10707
+ const result = await tool.handler(call.args, call.id);
10708
+ if (tool.render !== void 0) {
10709
+ this.#renderToolOutput(tool.render, call);
10710
+ }
10241
10711
  if (navigates) {
10242
10712
  card.settle(TOOL_CALL_STATUS.DONE, this.#strings.navigating);
10243
10713
  return { content: "", halt: true };
@@ -10356,7 +10826,13 @@ var AgUiChat = class extends HTMLElement {
10356
10826
  this.#runTools.push({ id: call.id, name: call.name });
10357
10827
  this.#cardFor(call);
10358
10828
  },
10359
- onActivity: (activityType, content) => {
10829
+ onActivity: (activityType, content, messageId) => {
10830
+ if (activityType === CHART_ACTIVITY_TYPE) {
10831
+ if (this.#chartActivity) {
10832
+ this.#drawActivityChart(messageId, content);
10833
+ }
10834
+ return;
10835
+ }
10360
10836
  if (activityType !== COMPACTION_ACTIVITY_TYPE) {
10361
10837
  return;
10362
10838
  }
@@ -10379,6 +10855,11 @@ var AgUiChat = class extends HTMLElement {
10379
10855
  this.#serverSettled.add(toolCallId);
10380
10856
  this.#showPending();
10381
10857
  },
10858
+ onActivityChanged: (messageId, activityType, content) => {
10859
+ if (activityType === CHART_ACTIVITY_TYPE && this.#chartActivity) {
10860
+ this.#drawActivityChart(messageId, content);
10861
+ }
10862
+ },
10382
10863
  onRunEnd: () => {
10383
10864
  this.#hidePending();
10384
10865
  this.#streamingBubble = null;
@@ -10526,8 +11007,84 @@ var AgUiChat = class extends HTMLElement {
10526
11007
  this.#appendNotice("\u2728", this.#strings.usingSkill.replace("{name}", skill), "skill");
10527
11008
  return true;
10528
11009
  }
10529
- #appendNotice(icon, text2, kind) {
10530
- this.#ensureGroup().appendChild(renderRunNotice(icon, text2, kind));
11010
+ #appendNotice(icon, text3, kind) {
11011
+ this.#ensureGroup().appendChild(renderRunNotice(icon, text3, kind));
11012
+ this.#updateEmptyState();
11013
+ this.#messages.scrollTop = this.#messages.scrollHeight;
11014
+ }
11015
+ /**
11016
+ * Turn on chart rendering, by whichever route this consumer wants.
11017
+ *
11018
+ * Both routes converge on one renderer deliberately. Built apart they become
11019
+ * two chart implementations with two sets of bugs, and the choice between them
11020
+ * is about *where the data lives* rather than how a bar should look:
11021
+ *
11022
+ * - `"tool"` registers the built-in `render_chart`. The agent decides a chart
11023
+ * helps and calls it, so the numbers are in its context and it can discuss
11024
+ * them afterwards. Costs one model round, and works over any transport.
11025
+ * - `"activity"` draws a server-pushed `ACTIVITY_SNAPSHOT` of type `chart`.
11026
+ * No round trip, and the data never enters the model's context at all —
11027
+ * which is what makes it the one for a large or sensitive dataset. Only this
11028
+ * route can update a chart in place as the server computes.
11029
+ *
11030
+ * Off unless asked for, both of them: a component that renders whatever
11031
+ * arrives is not something to switch on for everybody.
11032
+ */
11033
+ enableCharts(routes = ["tool", "activity"]) {
11034
+ const first = !this.#chartActivity && !this.#toolRegistry.has(CHART_TOOL_NAME);
11035
+ if (routes.includes("activity")) {
11036
+ this.#chartActivity = true;
11037
+ }
11038
+ if (routes.includes("tool")) {
11039
+ this.registerTool(createChartTool());
11040
+ }
11041
+ if (first && this.isConnected) {
11042
+ this.reload();
11043
+ }
11044
+ }
11045
+ /**
11046
+ * Place a tool's rendered node against its own card.
11047
+ *
11048
+ * Anchored rather than appended because a client tool's handler does not run
11049
+ * until the round is over: appending would put the node after everything the
11050
+ * model said next, visibly detached from the call that produced it, and in a
11051
+ * different order than the same transcript takes on reload. The card was
11052
+ * created inline, in the right place, so anchoring makes *when* the handler
11053
+ * runs stop mattering.
11054
+ */
11055
+ #renderToolOutput(render, call) {
11056
+ let node;
11057
+ try {
11058
+ node = render(call.args);
11059
+ } catch (error) {
11060
+ console.warn(`ag-ui-chat: render failed for tool ${call.name}`, error);
11061
+ return;
11062
+ }
11063
+ if (node === null) {
11064
+ return;
11065
+ }
11066
+ this.#cardElements.get(call.id)?.after(node);
11067
+ this.#afterTranscriptGrew();
11068
+ }
11069
+ /** Draw, or redraw in place, the chart for one activity message. */
11070
+ #drawActivityChart(messageId, content) {
11071
+ const spec = chartSpecFrom(content);
11072
+ const block = spec === null ? null : renderChart(spec);
11073
+ if (block === null) {
11074
+ this.#activityBlocks.get(messageId)?.remove();
11075
+ this.#activityBlocks.delete(messageId);
11076
+ return;
11077
+ }
11078
+ const existing = this.#activityBlocks.get(messageId);
11079
+ if (existing === void 0) {
11080
+ this.#ensureGroup().appendChild(block);
11081
+ } else {
11082
+ existing.replaceWith(block);
11083
+ }
11084
+ this.#activityBlocks.set(messageId, block);
11085
+ this.#afterTranscriptGrew();
11086
+ }
11087
+ #afterTranscriptGrew() {
10531
11088
  this.#updateEmptyState();
10532
11089
  this.#messages.scrollTop = this.#messages.scrollHeight;
10533
11090
  }
@@ -10580,43 +11137,45 @@ function defineAgUiChat() {
10580
11137
  }
10581
11138
 
10582
11139
  // src/dom/dom_driver.ts
10583
- async function fillField(el, value, options = {}) {
10584
- await scrollIntoCenterView(el);
10585
- await focusWithFlash(el, { ...options, flashMs: options.flashMs ?? 0 });
10586
- await typeInto(el, value, options);
10587
- }
10588
- async function clickElement(el, options = {}) {
10589
- await scrollIntoCenterView(el);
10590
- await highlightThenClick(el, options);
10591
- }
10592
- async function pressButton(el, options = {}) {
10593
- await scrollIntoCenterView(el);
10594
- await pressThenClick(el, options);
10595
- }
10596
- async function selectControl(el, value, options = {}) {
10597
- await scrollIntoCenterView(el);
10598
- await selectOption(el, value, options);
10599
- }
10600
- async function toggleCheckbox(el, checked, options = {}) {
10601
- await scrollIntoCenterView(el);
10602
- await toggleControl(el, checked, options);
10603
- }
10604
- function setControlValue(el, value) {
10605
- if (el instanceof HTMLInputElement && el.type === "checkbox") {
10606
- setNativeChecked(el, Boolean(value));
11140
+ async function fillField(el2, value, options = {}) {
11141
+ await scrollIntoCenterView(el2);
11142
+ await focusWithFlash(el2, { ...options, flashMs: options.flashMs ?? 0 });
11143
+ await typeInto(el2, value, options);
11144
+ }
11145
+ async function clickElement(el2, options = {}) {
11146
+ await scrollIntoCenterView(el2);
11147
+ await highlightThenClick(el2, options);
11148
+ }
11149
+ async function pressButton(el2, options = {}) {
11150
+ await scrollIntoCenterView(el2);
11151
+ await pressThenClick(el2, options);
11152
+ }
11153
+ async function selectControl(el2, value, options = {}) {
11154
+ await scrollIntoCenterView(el2);
11155
+ await selectOption(el2, value, options);
11156
+ }
11157
+ async function toggleCheckbox(el2, checked, options = {}) {
11158
+ await scrollIntoCenterView(el2);
11159
+ await toggleControl(el2, checked, options);
11160
+ }
11161
+ function setControlValue(el2, value) {
11162
+ if (el2 instanceof HTMLInputElement && el2.type === "checkbox") {
11163
+ setNativeChecked(el2, Boolean(value));
10607
11164
  } else {
10608
- setNativeValue(el, String(value));
11165
+ setNativeValue(el2, String(value));
10609
11166
  }
10610
- el.dispatchEvent(new Event("input", { bubbles: true }));
10611
- el.dispatchEvent(new Event("change", { bubbles: true }));
11167
+ el2.dispatchEvent(new Event("input", { bubbles: true }));
11168
+ el2.dispatchEvent(new Event("change", { bubbles: true }));
10612
11169
  }
10613
11170
 
10614
11171
  // src/version.ts
10615
- var VERSION = "0.25.2";
11172
+ var VERSION = "0.26.1";
10616
11173
  export {
10617
11174
  ATTACHMENT_EVENT,
10618
11175
  AgUiChat,
10619
11176
  AgUiClient,
11177
+ CHART_ACTIVITY_TYPE,
11178
+ CHART_TOOL_NAME,
10620
11179
  COMPACTION_ACTIVITY_TYPE,
10621
11180
  CheckpointMenu,
10622
11181
  ClientToolRegistry,
@@ -10643,6 +11202,7 @@ export {
10643
11202
  X_DESTRUCTIVE_KEY,
10644
11203
  X_NAVIGATES_KEY,
10645
11204
  X_SUMMARY_KEY,
11205
+ chartSpecFrom,
10646
11206
  clickElement,
10647
11207
  createHttpAgent,
10648
11208
  createPageActionTools,
@@ -10664,6 +11224,7 @@ export {
10664
11224
  pressButton,
10665
11225
  pressThenClick,
10666
11226
  prettifyToolName,
11227
+ renderChart,
10667
11228
  renderMarkdown,
10668
11229
  requestApproval,
10669
11230
  requestConfirmation,