@icen.ai/ui 0.8.0 → 0.9.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.
Files changed (228) hide show
  1. package/AGENTS.md +63 -14
  2. package/README.md +17 -9
  3. package/dist/base.css +4 -3
  4. package/dist/behaviors/accordion.d.mts +5 -2
  5. package/dist/behaviors/accordion.mjs +36 -11
  6. package/dist/behaviors/ai-branch.d.mts +19 -0
  7. package/dist/behaviors/ai-branch.mjs +217 -0
  8. package/dist/behaviors/ai-chat.d.mts +11 -7
  9. package/dist/behaviors/ai-chat.mjs +134 -51
  10. package/dist/behaviors/ai-composer.d.mts +28 -3
  11. package/dist/behaviors/ai-composer.mjs +399 -229
  12. package/dist/behaviors/ai-core.d.mts +9 -4
  13. package/dist/behaviors/ai-core.mjs +21 -8
  14. package/dist/behaviors/ai-diff.d.mts +2 -2
  15. package/dist/behaviors/ai-diff.mjs +172 -41
  16. package/dist/behaviors/ai-feedback.d.mts +26 -0
  17. package/dist/behaviors/ai-feedback.mjs +303 -0
  18. package/dist/behaviors/ai-panel.d.mts +26 -9
  19. package/dist/behaviors/ai-panel.mjs +211 -110
  20. package/dist/behaviors/ai-provider.mjs +54 -48
  21. package/dist/behaviors/ai-threads.d.mts +38 -0
  22. package/dist/behaviors/ai-threads.mjs +319 -0
  23. package/dist/behaviors/ai-tool.d.mts +12 -4
  24. package/dist/behaviors/ai-tool.mjs +316 -263
  25. package/dist/behaviors/ai-tools.d.mts +9 -6
  26. package/dist/behaviors/ai-tools.mjs +264 -155
  27. package/dist/behaviors/back-top.mjs +16 -1
  28. package/dist/behaviors/carousel.d.mts +5 -2
  29. package/dist/behaviors/carousel.mjs +53 -26
  30. package/dist/behaviors/charts.d.mts +170 -22
  31. package/dist/behaviors/charts.mjs +1016 -150
  32. package/dist/behaviors/command-palette.d.mts +40 -6
  33. package/dist/behaviors/command-palette.mjs +143 -15
  34. package/dist/behaviors/context-menu.d.mts +21 -4
  35. package/dist/behaviors/context-menu.mjs +122 -38
  36. package/dist/behaviors/controls.d.mts +59 -0
  37. package/dist/behaviors/controls.mjs +601 -0
  38. package/dist/behaviors/copy.d.mts +5 -2
  39. package/dist/behaviors/copy.mjs +37 -12
  40. package/dist/behaviors/datatable.d.mts +12 -0
  41. package/dist/behaviors/datatable.mjs +226 -43
  42. package/dist/behaviors/date-picker.d.mts +2 -2
  43. package/dist/behaviors/date-picker.mjs +171 -32
  44. package/dist/behaviors/dropdown.d.mts +6 -2
  45. package/dist/behaviors/dropdown.mjs +164 -25
  46. package/dist/behaviors/events.d.mts +523 -0
  47. package/dist/behaviors/events.mjs +208 -0
  48. package/dist/behaviors/input.mjs +25 -12
  49. package/dist/behaviors/kb-access.d.mts +34 -0
  50. package/dist/behaviors/kb-access.mjs +569 -0
  51. package/dist/behaviors/kb-acl.d.mts +50 -0
  52. package/dist/behaviors/kb-acl.mjs +405 -0
  53. package/dist/behaviors/kb-answer.d.mts +38 -0
  54. package/dist/behaviors/kb-answer.mjs +2055 -0
  55. package/dist/behaviors/kb-audit.d.mts +23 -0
  56. package/dist/behaviors/kb-audit.mjs +375 -0
  57. package/dist/behaviors/kb-canvas.d.mts +44 -0
  58. package/dist/behaviors/kb-canvas.mjs +589 -0
  59. package/dist/behaviors/kb-chain.d.mts +15 -0
  60. package/dist/behaviors/kb-chain.mjs +270 -0
  61. package/dist/behaviors/kb-checkpoint.d.mts +16 -0
  62. package/dist/behaviors/kb-checkpoint.mjs +342 -0
  63. package/dist/behaviors/kb-chunks.d.mts +31 -0
  64. package/dist/behaviors/kb-chunks.mjs +475 -0
  65. package/dist/behaviors/kb-citation.d.mts +38 -0
  66. package/dist/behaviors/kb-citation.mjs +677 -0
  67. package/dist/behaviors/kb-clarify.d.mts +20 -0
  68. package/dist/behaviors/kb-clarify.mjs +248 -0
  69. package/dist/behaviors/kb-conflict.d.mts +22 -0
  70. package/dist/behaviors/kb-conflict.mjs +284 -0
  71. package/dist/behaviors/kb-connector.d.mts +17 -0
  72. package/dist/behaviors/kb-connector.mjs +375 -0
  73. package/dist/behaviors/kb-core.d.mts +529 -0
  74. package/dist/behaviors/kb-core.mjs +890 -0
  75. package/dist/behaviors/kb-eval.d.mts +22 -0
  76. package/dist/behaviors/kb-eval.mjs +408 -0
  77. package/dist/behaviors/kb-explain.d.mts +11 -0
  78. package/dist/behaviors/kb-explain.mjs +243 -0
  79. package/dist/behaviors/kb-filter.d.mts +31 -0
  80. package/dist/behaviors/kb-filter.mjs +587 -0
  81. package/dist/behaviors/kb-gap.d.mts +27 -0
  82. package/dist/behaviors/kb-gap.mjs +273 -0
  83. package/dist/behaviors/kb-hittest.d.mts +34 -0
  84. package/dist/behaviors/kb-hittest.mjs +409 -0
  85. package/dist/behaviors/kb-metadata.d.mts +29 -0
  86. package/dist/behaviors/kb-metadata.mjs +326 -0
  87. package/dist/behaviors/kb-passage.d.mts +41 -0
  88. package/dist/behaviors/kb-passage.mjs +529 -0
  89. package/dist/behaviors/kb-pipeline.d.mts +18 -0
  90. package/dist/behaviors/kb-pipeline.mjs +381 -0
  91. package/dist/behaviors/kb-qa.d.mts +26 -0
  92. package/dist/behaviors/kb-qa.mjs +332 -0
  93. package/dist/behaviors/kb-rerank.d.mts +20 -0
  94. package/dist/behaviors/kb-rerank.mjs +315 -0
  95. package/dist/behaviors/kb-retrieval.d.mts +49 -0
  96. package/dist/behaviors/kb-retrieval.mjs +746 -0
  97. package/dist/behaviors/kb-review.d.mts +44 -0
  98. package/dist/behaviors/kb-review.mjs +451 -0
  99. package/dist/behaviors/kb-sandbox.d.mts +33 -0
  100. package/dist/behaviors/kb-sandbox.mjs +325 -0
  101. package/dist/behaviors/kb-segment.d.mts +32 -0
  102. package/dist/behaviors/kb-segment.mjs +499 -0
  103. package/dist/behaviors/kb-sources.d.mts +27 -0
  104. package/dist/behaviors/kb-sources.mjs +373 -0
  105. package/dist/behaviors/kb-sql.d.mts +43 -0
  106. package/dist/behaviors/kb-sql.mjs +948 -0
  107. package/dist/behaviors/kb-trace.d.mts +16 -0
  108. package/dist/behaviors/kb-trace.mjs +414 -0
  109. package/dist/behaviors/kb-visibility.d.mts +44 -0
  110. package/dist/behaviors/kb-visibility.mjs +403 -0
  111. package/dist/behaviors/kb-who-can.d.mts +32 -0
  112. package/dist/behaviors/kb-who-can.mjs +379 -0
  113. package/dist/behaviors/modal.d.mts +24 -5
  114. package/dist/behaviors/modal.mjs +76 -10
  115. package/dist/behaviors/nav.d.mts +1 -1
  116. package/dist/behaviors/nav.mjs +33 -7
  117. package/dist/behaviors/notification.d.mts +20 -4
  118. package/dist/behaviors/notification.mjs +53 -73
  119. package/dist/behaviors/popover.d.mts +39 -3
  120. package/dist/behaviors/popover.mjs +90 -4
  121. package/dist/behaviors/select.d.mts +4 -2
  122. package/dist/behaviors/select.mjs +161 -24
  123. package/dist/behaviors/sidebar.d.mts +5 -2
  124. package/dist/behaviors/sidebar.mjs +43 -13
  125. package/dist/behaviors/slider.d.mts +5 -2
  126. package/dist/behaviors/slider.mjs +54 -43
  127. package/dist/behaviors/split-pane.d.mts +6 -2
  128. package/dist/behaviors/split-pane.mjs +62 -21
  129. package/dist/behaviors/tabs.d.mts +3 -2
  130. package/dist/behaviors/tabs.mjs +65 -14
  131. package/dist/behaviors/tag-input.d.mts +5 -2
  132. package/dist/behaviors/tag-input.mjs +61 -21
  133. package/dist/behaviors/theme.d.mts +13 -2
  134. package/dist/behaviors/theme.mjs +48 -4
  135. package/dist/behaviors/toast.d.mts +30 -8
  136. package/dist/behaviors/toast.mjs +74 -7
  137. package/dist/behaviors/tree.d.mts +5 -2
  138. package/dist/behaviors/tree.mjs +38 -15
  139. package/dist/behaviors/upload.mjs +21 -23
  140. package/dist/cli.mjs +1 -1
  141. package/dist/components/ai-branch.mjs +3 -0
  142. package/dist/components/ai-chat.css +295 -0
  143. package/dist/components/ai-feedback.mjs +3 -0
  144. package/dist/components/ai-panel.css +218 -1
  145. package/dist/components/ai-threads.mjs +3 -0
  146. package/dist/components/ai-tool-call.mjs +1 -1
  147. package/dist/components/badge.css +1 -1
  148. package/dist/components/breadcrumb.css +0 -32
  149. package/dist/components/chart-calendar.mjs +1 -2
  150. package/dist/components/chart-graph.css +57 -0
  151. package/dist/components/chart-graph.mjs +4 -0
  152. package/dist/components/chart-map.mjs +3 -0
  153. package/dist/components/chart-pie.css +0 -6
  154. package/dist/components/chart-scatter.mjs +1 -1
  155. package/dist/components/charts.css +33 -1
  156. package/dist/components/charts.mjs +1 -0
  157. package/dist/components/content.css +3 -16
  158. package/dist/components/datatable.css +1 -1
  159. package/dist/components/date-picker.css +1 -1
  160. package/dist/components/kb-access.mjs +4 -0
  161. package/dist/components/kb-acl.mjs +4 -0
  162. package/dist/components/kb-agent.css +481 -0
  163. package/dist/components/kb-answer.mjs +4 -0
  164. package/dist/components/kb-audit.mjs +4 -0
  165. package/dist/components/kb-canvas.mjs +4 -0
  166. package/dist/components/kb-chain.mjs +4 -0
  167. package/dist/components/kb-checkpoint.mjs +4 -0
  168. package/dist/components/kb-chunks.mjs +4 -0
  169. package/dist/components/kb-citation.mjs +4 -0
  170. package/dist/components/kb-clarify.mjs +4 -0
  171. package/dist/components/kb-conflict.mjs +4 -0
  172. package/dist/components/kb-connector.mjs +4 -0
  173. package/dist/components/kb-data.css +649 -0
  174. package/dist/components/kb-eval.mjs +4 -0
  175. package/dist/components/kb-explain.mjs +4 -0
  176. package/dist/components/kb-filter.mjs +4 -0
  177. package/dist/components/kb-gap.mjs +4 -0
  178. package/dist/components/kb-ground.css +566 -0
  179. package/dist/components/kb-hittest.mjs +4 -0
  180. package/dist/components/kb-ingest.css +898 -0
  181. package/dist/components/kb-metadata.mjs +4 -0
  182. package/dist/components/kb-ops.css +548 -0
  183. package/dist/components/kb-passage.mjs +4 -0
  184. package/dist/components/kb-perm.css +444 -0
  185. package/dist/components/kb-pipeline.mjs +4 -0
  186. package/dist/components/kb-qa.mjs +4 -0
  187. package/dist/components/kb-rerank.mjs +4 -0
  188. package/dist/components/kb-retrieval.mjs +4 -0
  189. package/dist/components/kb-review.mjs +4 -0
  190. package/dist/components/kb-sandbox.mjs +4 -0
  191. package/dist/components/kb-search.css +940 -0
  192. package/dist/components/kb-segment.mjs +4 -0
  193. package/dist/components/kb-sources.mjs +4 -0
  194. package/dist/components/kb-sql.mjs +4 -0
  195. package/dist/components/kb-trace.mjs +4 -0
  196. package/dist/components/kb-visibility.mjs +4 -0
  197. package/dist/components/kb-who-can.mjs +4 -0
  198. package/dist/components/kb.css +252 -0
  199. package/dist/components/kb.mjs +9 -0
  200. package/dist/components/notification.css +2 -3
  201. package/dist/components/pagination.css +3 -1
  202. package/dist/components/pagination.mjs +1 -0
  203. package/dist/components/rating.mjs +1 -0
  204. package/dist/components/segmented.css +9 -1
  205. package/dist/components/segmented.mjs +1 -1
  206. package/dist/components/select.css +67 -4
  207. package/dist/components/steps.css +13 -3
  208. package/dist/components/steps.mjs +1 -0
  209. package/dist/components/switch.css +3 -1
  210. package/dist/components/switch.mjs +1 -0
  211. package/dist/components/table.mjs +1 -0
  212. package/dist/components/tabs.css +23 -0
  213. package/dist/components/tag-input.css +1 -6
  214. package/dist/components/upload.css +0 -15
  215. package/dist/index.d.mts +48 -11
  216. package/dist/index.mjs +13205 -3615
  217. package/dist/registry.json +366 -15
  218. package/dist/retro-effects.css +3 -16
  219. package/dist/tokens/retro-effects.css +3 -16
  220. package/dist/tokens/style-profiles.css +3 -2
  221. package/dist/tokens/typography.css +2 -0
  222. package/dist/tokens.css +5 -2
  223. package/dist/ui.css +5713 -267
  224. package/docs/research/2026-10-03-ai-kb-components.md +392 -0
  225. package/docs/research/2026-10-03-kb-permissions.md +992 -0
  226. package/docs/spec/ai-native.md +7 -4
  227. package/docs/spec/kb-family.md +443 -0
  228. package/package.json +2 -2
@@ -1,4 +1,13 @@
1
1
  // src/behaviors/ai-core.ts
2
+ var AI_STATUSES = [
3
+ "pending",
4
+ "running",
5
+ "streaming",
6
+ "approval",
7
+ "done",
8
+ "error",
9
+ "cancelled"
10
+ ];
2
11
  function aiStatusLabel(s) {
3
12
  const labels = {
4
13
  pending: "\u7B49\u5F85",
@@ -171,9 +180,15 @@ function formatDuration(ms) {
171
180
  const m = Math.floor(ms / 6e4);
172
181
  const s = Math.round(ms % 6e4 / 1e3);
173
182
  if (ms < 36e5) return s > 0 ? `${m}m${s}s` : `${m}m`;
174
- const h = Math.floor(ms / 36e5);
183
+ const h2 = Math.floor(ms / 36e5);
175
184
  const rm = Math.round(ms % 36e5 / 6e4);
176
- return rm > 0 ? `${h}h${rm}m` : `${h}h`;
185
+ return rm > 0 ? `${h2}h${rm}m` : `${h2}h`;
186
+ }
187
+ function h(tag, className, text) {
188
+ const node = document.createElement(tag);
189
+ if (className) node.className = className;
190
+ if (text != null) node.textContent = text;
191
+ return node;
177
192
  }
178
193
  function svgIcon(svg2) {
179
194
  if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
@@ -187,15 +202,15 @@ function svgIcon(svg2) {
187
202
  if (!root || root.tagName.toLowerCase() !== "svg" || parsed.querySelector("parsererror")) {
188
203
  return null;
189
204
  }
190
- const sanitize = (el2) => {
191
- for (const attr of Array.from(el2.attributes)) {
205
+ const sanitize = (el) => {
206
+ for (const attr of Array.from(el.attributes)) {
192
207
  const name = attr.name.toLowerCase();
193
- if (name.startsWith("on")) el2.removeAttribute(attr.name);
208
+ if (name.startsWith("on")) el.removeAttribute(attr.name);
194
209
  if ((name === "href" || name === "xlink:href") && attr.value.trim().toLowerCase().startsWith("javascript:")) {
195
- el2.removeAttribute(attr.name);
210
+ el.removeAttribute(attr.name);
196
211
  }
197
212
  }
198
- for (const child of Array.from(el2.children)) {
213
+ for (const child of Array.from(el.children)) {
199
214
  const tag = child.tagName.toLowerCase();
200
215
  if (tag === "script" || tag === "foreignobject") {
201
216
  child.remove();
@@ -209,6 +224,44 @@ function svgIcon(svg2) {
209
224
  }
210
225
 
211
226
  // src/behaviors/popover.ts
227
+ var PANEL_SIZING_ATTRS = {
228
+ width: "data-panel-width",
229
+ minWidth: "data-panel-min",
230
+ maxWidth: "data-panel-max",
231
+ minHeight: "data-panel-min-height",
232
+ maxHeight: "data-panel-max-height"
233
+ };
234
+ function readPanelSizing(el) {
235
+ const out = {};
236
+ if (!el) return out;
237
+ for (const key of Object.keys(PANEL_SIZING_ATTRS)) {
238
+ const raw = el.getAttribute(PANEL_SIZING_ATTRS[key]);
239
+ if (raw == null) continue;
240
+ const n = Number(raw);
241
+ if (Number.isFinite(n) && n > 0) out[key] = n;
242
+ }
243
+ return out;
244
+ }
245
+ function resolvePanelSizing(el, overrides) {
246
+ const base = readPanelSizing(el);
247
+ if (!overrides) return base;
248
+ for (const key of Object.keys(overrides)) {
249
+ const v = overrides[key];
250
+ if (typeof v === "number" && Number.isFinite(v) && v > 0) base[key] = v;
251
+ }
252
+ return base;
253
+ }
254
+ function sizingToLayout(sizing, opts) {
255
+ const out = { ...opts };
256
+ if (sizing.minWidth != null) out.minWidth = sizing.minWidth;
257
+ if (sizing.maxWidth != null) out.maxWidth = sizing.maxWidth;
258
+ if (sizing.width != null) {
259
+ out.minWidth = sizing.width;
260
+ out.maxWidth = sizing.width;
261
+ }
262
+ if (sizing.maxHeight != null) out.maxHeight = sizing.maxHeight;
263
+ return out;
264
+ }
212
265
  var DEFAULT_MIN_WIDTH = 220;
213
266
  var DEFAULT_MAX_WIDTH = 420;
214
267
  var DEFAULT_MIN_HEIGHT = 120;
@@ -233,13 +286,35 @@ function horizontalAnchor(anchor, width, align) {
233
286
  function computePopoverLayout(anchor, viewport, opts = {}) {
234
287
  const margin = opts.margin ?? 12;
235
288
  const offset = opts.offset ?? 8;
289
+ const preferredSide = opts.side ?? "top";
290
+ const align = opts.align ?? "start";
291
+ if (preferredSide === "left" || preferredSide === "right") {
292
+ const availableLeft = Math.max(0, anchor.left - margin - offset);
293
+ const availableRight = Math.max(0, viewport.width - anchor.right - margin - offset);
294
+ const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= DEFAULT_MIN_WIDTH ? preferredSide : preferredSide === "left" ? "right" : "left";
295
+ const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
296
+ const maxWidth2 = Math.min(
297
+ Math.max(opts.maxWidth ?? DEFAULT_MAX_WIDTH, minWidth2),
298
+ side2 === "left" ? availableLeft : availableRight
299
+ );
300
+ const width2 = minWidth2;
301
+ const left2 = clamp(
302
+ side2 === "left" ? anchor.left - offset - width2 : anchor.right + offset,
303
+ margin,
304
+ viewport.width - margin - width2
305
+ );
306
+ const availableHeight2 = Math.max(80, viewport.height - margin * 2);
307
+ const maxHeight2 = Math.min(opts.maxHeight ?? DEFAULT_MAX_HEIGHT, availableHeight2);
308
+ const desired = clamp(opts.contentHeightHint ?? Math.min(anchor.height, maxHeight2), 40, maxHeight2);
309
+ const anchorTop = align === "center" ? anchor.top + anchor.height / 2 - desired / 2 : align === "end" ? anchor.bottom - desired : anchor.top;
310
+ const top2 = clamp(anchorTop, margin, viewport.height - margin - desired);
311
+ return { side: side2, left: left2, top: top2, width: width2, maxHeight: maxHeight2 };
312
+ }
236
313
  const availableWidth = Math.max(80, viewport.width - margin * 2);
237
314
  const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
238
315
  const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
239
316
  const maxWidth = Math.min(requestedMaxWidth, availableWidth);
240
317
  const minWidth = Math.min(requestedMinWidth, maxWidth);
241
- const preferredSide = opts.side ?? "top";
242
- const align = opts.align ?? "start";
243
318
  const availableTop = Math.max(0, anchor.top - margin - offset);
244
319
  const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
245
320
  const availableHeight = Math.max(80, viewport.height - margin * 2);
@@ -263,8 +338,8 @@ function computePopoverLayout(anchor, viewport, opts = {}) {
263
338
  return { side, left, top, width, maxHeight };
264
339
  }
265
340
  var openPanels = /* @__PURE__ */ new Map();
266
- function rectFromElement(el2) {
267
- const r = el2.getBoundingClientRect();
341
+ function rectFromElement(el) {
342
+ const r = el.getBoundingClientRect();
268
343
  return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
269
344
  }
270
345
  function resolveAnchor(state) {
@@ -317,7 +392,8 @@ function onKeyDown(e) {
317
392
  function openPopover(panel, options) {
318
393
  if (typeof document === "undefined") return;
319
394
  if (openPanels.has(panel)) closePopover(panel);
320
- const { anchor, onClose, ...layoutOpts } = options;
395
+ const { anchor, onClose, sizing, ...rest } = options;
396
+ const layoutOpts = sizingToLayout(resolvePanelSizing(panel, sizing), rest);
321
397
  const placeholder = document.createComment("popover-anchor");
322
398
  panel.parentNode?.insertBefore(placeholder, panel);
323
399
  const wasHidden = panel.hidden;
@@ -369,32 +445,24 @@ function closePopover(panel) {
369
445
  state.onClose?.();
370
446
  }
371
447
 
372
- // src/behaviors/ai-panel.ts
373
- function el(tag, className, text) {
374
- const node = document.createElement(tag);
375
- if (className) node.className = className;
376
- if (text !== void 0) node.textContent = text;
377
- return node;
448
+ // src/behaviors/events.ts
449
+ function emitIcen(source, type, detail) {
450
+ return source.dispatchEvent(
451
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
452
+ );
378
453
  }
454
+
455
+ // src/behaviors/ai-panel.ts
379
456
  function dispatch(node, name, detail) {
380
- node.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }));
457
+ emitIcen(node, name, detail);
381
458
  }
382
- var STATUS_CLASSES = [
383
- "pending",
384
- "running",
385
- "streaming",
386
- "approval",
387
- "done",
388
- "error",
389
- "cancelled"
390
- ];
391
459
  function stripStatusClasses(node) {
392
- for (const s of STATUS_CLASSES) node.classList.remove(`is-${s}`);
460
+ for (const s of AI_STATUSES) node.classList.remove(`is-${s}`);
393
461
  }
394
462
  function buildTodoHead(done, total) {
395
- const head = el("div", "ai-todo-head");
396
- head.appendChild(el("span", "ai-todo-progress", `${done}/${total}`));
397
- const bar = el("div", "ai-todo-bar");
463
+ const head = h("div", "ai-todo-head");
464
+ head.appendChild(h("span", "ai-todo-progress", `${done}/${total}`));
465
+ const bar = h("div", "ai-todo-bar");
398
466
  bar.setAttribute("role", "progressbar");
399
467
  bar.setAttribute("aria-valuemin", "0");
400
468
  bar.setAttribute("aria-valuemax", String(total));
@@ -406,24 +474,31 @@ function buildTodoHead(done, total) {
406
474
  head.appendChild(bar);
407
475
  return head;
408
476
  }
477
+ var ICON_TODO_CHECK = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m3.5 12.5 5 5L20.5 6" pathLength="1"/></svg>';
409
478
  function buildTodoItem(item, index) {
410
- const row = el("div", `ai-todo-item is-${item.status}`);
479
+ const row = h("div", `ai-todo-item is-${item.status}`);
411
480
  row.dataset.index = String(index);
412
- row.appendChild(el("span", "ai-item-status"));
413
- row.appendChild(el("span", "ai-todo-text", item.content));
414
- row.appendChild(el("span", "ai-todo-active", item.activeForm ?? item.content));
481
+ row.appendChild(h("span", "ai-item-status"));
482
+ const check = h("span", "ai-todo-check is-anim");
483
+ check.setAttribute("aria-hidden", "true");
484
+ const checkSvg = svgIcon(ICON_TODO_CHECK);
485
+ if (checkSvg) check.appendChild(checkSvg);
486
+ row.appendChild(check);
487
+ row.appendChild(h("span", "ai-todo-text", item.content));
488
+ row.appendChild(h("span", "ai-todo-active", item.activeForm ?? item.content));
415
489
  return row;
416
490
  }
417
- function renderAiTodo(elm, items) {
418
- if (typeof document === "undefined") return;
419
- elm.textContent = "";
420
- const root = el("div", "ai-todo");
491
+ function renderAiTodo(el, items) {
492
+ if (typeof document === "undefined") return el;
493
+ el.textContent = "";
494
+ const root = h("div", "ai-todo");
421
495
  const list = Array.isArray(items) ? items : [];
422
496
  const done = list.filter((i) => i.status === "done").length;
423
497
  root.appendChild(buildTodoHead(done, list.length));
424
498
  list.forEach((item, i) => root.appendChild(buildTodoItem(item, i)));
425
499
  if (list.length > 0 && done === list.length) root.classList.add("is-complete");
426
- elm.appendChild(root);
500
+ el.appendChild(root);
501
+ return el;
427
502
  }
428
503
  var TODO_NEXT = {
429
504
  pending: "running",
@@ -431,7 +506,7 @@ var TODO_NEXT = {
431
506
  done: "pending"
432
507
  };
433
508
  function todoItemStatus(item) {
434
- for (const s of STATUS_CLASSES) {
509
+ for (const s of AI_STATUSES) {
435
510
  if (item.classList.contains(`is-${s}`)) return s;
436
511
  }
437
512
  return "pending";
@@ -512,8 +587,20 @@ function initAiTodo(root) {
512
587
  marked.__icenAiTodoInit = false;
513
588
  };
514
589
  }
515
- function renderAiUsage(elm, usage, opts = {}) {
516
- if (typeof document === "undefined") return;
590
+ function metricField(src, key) {
591
+ return typeof src === "object" && src !== null ? src[key] : void 0;
592
+ }
593
+ function metricNum(v) {
594
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
595
+ }
596
+ function metricStr(v) {
597
+ return typeof v === "string" && v ? v : void 0;
598
+ }
599
+ function trimNum(v) {
600
+ return String(Math.round(v * 10) / 10);
601
+ }
602
+ function renderAiUsage(el, usage, opts = {}) {
603
+ if (typeof document === "undefined") return el;
517
604
  const u = normalizeUsage(usage);
518
605
  const segs = [
519
606
  { key: "input", className: "ai-usage-seg--input", label: "\u8F93\u5165", value: u.input ?? 0 },
@@ -525,9 +612,9 @@ function renderAiUsage(elm, usage, opts = {}) {
525
612
  const activeSegs = segs.filter((s) => s.value > 0);
526
613
  const sum2 = activeSegs.reduce((acc, s) => acc + s.value, 0);
527
614
  const total = u.total ?? sum2;
528
- elm.textContent = "";
529
- const root = el("div", "ai-usage");
530
- const bar = el("div", "ai-usage-bar");
615
+ el.textContent = "";
616
+ const root = h("div", "ai-usage");
617
+ const bar = h("div", "ai-usage-bar");
531
618
  const summary = activeSegs.length > 0 ? activeSegs.map((s) => `${s.label} ${formatTokens(s.value)}`).join("\uFF0C") : "\u6682\u65E0\u7528\u91CF";
532
619
  bar.setAttribute("role", "img");
533
620
  bar.setAttribute("aria-label", `\u7528\u91CF\uFF1A${summary}`);
@@ -539,19 +626,27 @@ function renderAiUsage(elm, usage, opts = {}) {
539
626
  }
540
627
  root.appendChild(bar);
541
628
  if (activeSegs.length > 0) {
542
- const legend = el("div", "ai-usage-legend");
629
+ const legend = h("div", "ai-usage-legend");
543
630
  for (const s of activeSegs) {
544
- const item = el("span", "ai-usage-legend-item");
631
+ const item = h("span", "ai-usage-legend-item");
545
632
  const dot = document.createElement("i");
546
633
  dot.className = `ai-usage-dot ${s.className}`;
547
634
  dot.setAttribute("aria-hidden", "true");
548
635
  item.appendChild(dot);
549
- item.appendChild(el("span", "ai-usage-legend-label", s.label));
550
- item.appendChild(el("span", "ai-usage-legend-value", formatTokens(s.value)));
636
+ item.appendChild(h("span", "ai-usage-legend-label", s.label));
637
+ item.appendChild(h("span", "ai-usage-legend-value", formatTokens(s.value)));
551
638
  legend.appendChild(item);
552
639
  }
553
640
  root.appendChild(legend);
554
641
  }
642
+ const ttft = metricNum(opts.metrics?.ttftMs) ?? metricNum(metricField(usage, "ttftMs"));
643
+ const tps = metricNum(opts.metrics?.tokensPerSec) ?? metricNum(metricField(usage, "tokensPerSec"));
644
+ const finish = metricStr(opts.metrics?.finishReason) ?? metricStr(metricField(usage, "finishReason"));
645
+ const metricBits = [];
646
+ if (ttft != null) metricBits.push(`TTFT ${formatDuration(ttft)}`);
647
+ if (tps != null && tps > 0) metricBits.push(`${trimNum(tps)} tok/s`);
648
+ if (finish) metricBits.push(`finish: ${finish}`);
649
+ if (metricBits.length > 0) root.appendChild(h("div", "ai-usage-metrics", metricBits.join(" \xB7 ")));
555
650
  const totalParts = [formatTokens(total)];
556
651
  if (typeof opts.total === "number" && opts.total > 0) {
557
652
  const pct = Math.min(100, Math.round(total / opts.total * 100));
@@ -560,17 +655,18 @@ function renderAiUsage(elm, usage, opts = {}) {
560
655
  if (typeof opts.cost === "number" && Number.isFinite(opts.cost)) {
561
656
  totalParts.push(` \xB7 $${opts.cost.toFixed(4)}`);
562
657
  }
563
- root.appendChild(el("div", "ai-usage-total", totalParts.join("")));
564
- elm.appendChild(root);
658
+ root.appendChild(h("div", "ai-usage-total", totalParts.join("")));
659
+ el.appendChild(root);
660
+ return el;
565
661
  }
566
- function renderAiUsageRing(elm, usage, opts = {}) {
567
- if (typeof document === "undefined") return;
662
+ function renderAiUsageRing(el, usage, opts = {}) {
663
+ if (typeof document === "undefined") return el;
568
664
  const u = normalizeUsage(usage);
569
665
  const used = u.total ?? (u.input ?? 0) + (u.output ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0) + (u.reasoning ?? 0);
570
666
  const hasLimit = typeof opts.total === "number" && opts.total > 0;
571
667
  const pct = hasLimit ? Math.min(100, Math.max(0, Math.round(used / opts.total * 100))) : 0;
572
- elm.textContent = "";
573
- const btn = el("button", "ai-usage-ring");
668
+ el.textContent = "";
669
+ const btn = h("button", "ai-usage-ring");
574
670
  btn.type = "button";
575
671
  btn.classList.add(pct >= 85 ? "is-hot" : pct >= 60 ? "is-warn" : "is-ok");
576
672
  btn.setAttribute("aria-haspopup", "true");
@@ -594,7 +690,7 @@ function renderAiUsageRing(elm, usage, opts = {}) {
594
690
  svg2.appendChild(c);
595
691
  }
596
692
  btn.appendChild(svg2);
597
- btn.appendChild(el("span", "ai-usage-ring-pct", hasLimit ? `${pct}%` : formatTokens(used)));
693
+ btn.appendChild(h("span", "ai-usage-ring-pct", hasLimit ? `${pct}%` : formatTokens(used)));
598
694
  let panel = null;
599
695
  const closePanel = () => {
600
696
  if (!panel) return;
@@ -608,10 +704,10 @@ function renderAiUsageRing(elm, usage, opts = {}) {
608
704
  closePanel();
609
705
  return;
610
706
  }
611
- const p = el("div", "popover ai-usage-popover");
707
+ const p = h("div", "popover ai-usage-popover");
612
708
  p.hidden = true;
613
- p.appendChild(el("div", "ai-usage-popover-title", opts.title ?? "\u4E0A\u4E0B\u6587\u7A97\u53E3"));
614
- const body = el("div", "ai-usage-popover-body");
709
+ p.appendChild(h("div", "ai-usage-popover-title", opts.title ?? "\u4E0A\u4E0B\u6587\u7A97\u53E3"));
710
+ const body = h("div", "ai-usage-popover-body");
615
711
  renderAiUsage(body, usage, opts);
616
712
  p.appendChild(body);
617
713
  openPopover(p, {
@@ -625,7 +721,8 @@ function renderAiUsageRing(elm, usage, opts = {}) {
625
721
  });
626
722
  panel = p;
627
723
  });
628
- elm.appendChild(btn);
724
+ el.appendChild(btn);
725
+ return el;
629
726
  }
630
727
  var svgWrap = (inner) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${inner}</svg>`;
631
728
  var FILE_OUTLINE = '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/>';
@@ -673,8 +770,8 @@ function fileKindIcon(kind) {
673
770
  }
674
771
  function renderFileChip(file) {
675
772
  const kind = fileKind(file.path);
676
- const chip = el("span", `ai-file-chip ai-file-chip--${kind} is-${file.status ?? "modified"}`);
677
- const icon = el("span", "ai-file-icon");
773
+ const chip = h("span", `ai-file-chip ai-file-chip--${kind} is-${file.status ?? "modified"}`);
774
+ const icon = h("span", "ai-file-icon");
678
775
  const svg2 = svgIcon(fileKindIcon(kind));
679
776
  if (svg2) icon.appendChild(svg2);
680
777
  chip.appendChild(icon);
@@ -682,21 +779,21 @@ function renderFileChip(file) {
682
779
  return chip;
683
780
  }
684
781
  function renderSection(title) {
685
- const section = el("section", "ai-context-section");
686
- section.appendChild(el("h3", "", title));
687
- const body = el("div", "ai-context-section-body");
782
+ const section = h("section", "ai-context-section");
783
+ section.appendChild(h("h3", "", title));
784
+ const body = h("div", "ai-context-section-body");
688
785
  section.appendChild(body);
689
786
  return { section, body };
690
787
  }
691
- function renderAiContext(elm, model) {
692
- if (typeof document === "undefined") return;
693
- elm.textContent = "";
788
+ function renderAiContext(el, model) {
789
+ if (typeof document === "undefined") return el;
790
+ el.textContent = "";
694
791
  const m = model ?? {};
695
- const aside = el("aside", "ai-context");
792
+ const aside = h("aside", "ai-context");
696
793
  aside.hidden = true;
697
- const head = el("div", "ai-context-head");
698
- head.appendChild(el("span", "", "\u4E0A\u4E0B\u6587"));
699
- const closeBtn = el("button", "", "\xD7");
794
+ const head = h("div", "ai-context-head");
795
+ head.appendChild(h("span", "", "\u4E0A\u4E0B\u6587"));
796
+ const closeBtn = h("button", "", "\xD7");
700
797
  closeBtn.type = "button";
701
798
  closeBtn.setAttribute("data-ai-context-close", "");
702
799
  closeBtn.setAttribute("aria-label", "\u5173\u95ED");
@@ -716,7 +813,7 @@ function renderAiContext(elm, model) {
716
813
  const files = Array.isArray(m.files) ? m.files : [];
717
814
  if (files.length > 0) {
718
815
  const { section, body } = renderSection(`\u6587\u4EF6 (${files.length})`);
719
- const chips = el("div", "ai-files");
816
+ const chips = h("div", "ai-files");
720
817
  for (const f of files) chips.appendChild(renderFileChip(f));
721
818
  body.appendChild(chips);
722
819
  aside.appendChild(section);
@@ -726,17 +823,17 @@ function renderAiContext(elm, model) {
726
823
  const { section, body } = renderSection(`MCP (${servers.length})`);
727
824
  for (const s of servers) {
728
825
  const ok = s.status !== "disconnected";
729
- const row = el("div", `ai-item is-${ok ? "done" : "error"}`);
730
- const icon = el("span", "ai-item-icon");
826
+ const row = h("div", `ai-item is-${ok ? "done" : "error"}`);
827
+ const icon = h("span", "ai-item-icon");
731
828
  const svg2 = svgIcon(ICON_SERVER);
732
829
  if (svg2) icon.appendChild(svg2);
733
830
  row.appendChild(icon);
734
- const main = el("span", "ai-item-main");
735
- main.appendChild(el("span", "ai-item-title", s.name));
831
+ const main = h("span", "ai-item-main");
832
+ main.appendChild(h("span", "ai-item-title", s.name));
736
833
  row.appendChild(main);
737
- const side = el("span", "ai-item-side");
738
- side.appendChild(el("span", "ai-item-status"));
739
- side.appendChild(el("span", "ai-item-meta", typeof s.tools === "number" ? `${s.tools} \u5DE5\u5177` : ""));
834
+ const side = h("span", "ai-item-side");
835
+ side.appendChild(h("span", "ai-item-status"));
836
+ side.appendChild(h("span", "ai-item-meta", typeof s.tools === "number" ? `${s.tools} \u5DE5\u5177` : ""));
740
837
  row.appendChild(side);
741
838
  body.appendChild(row);
742
839
  }
@@ -746,20 +843,21 @@ function renderAiContext(elm, model) {
746
843
  if (skills.length > 0) {
747
844
  const { section, body } = renderSection("Skills");
748
845
  for (const s of skills) {
749
- const row = el("div", "ai-item");
750
- const icon = el("span", "ai-item-icon");
846
+ const row = h("div", "ai-item");
847
+ const icon = h("span", "ai-item-icon");
751
848
  const svg2 = svgIcon(ICON_SPARKLES);
752
849
  if (svg2) icon.appendChild(svg2);
753
850
  row.appendChild(icon);
754
- const main = el("span", "ai-item-main");
755
- main.appendChild(el("span", "ai-item-title", s.name));
756
- if (s.description) main.appendChild(el("span", "ai-item-sub", s.description));
851
+ const main = h("span", "ai-item-main");
852
+ main.appendChild(h("span", "ai-item-title", s.name));
853
+ if (s.description) main.appendChild(h("span", "ai-item-sub", s.description));
757
854
  row.appendChild(main);
758
855
  body.appendChild(row);
759
856
  }
760
857
  aside.appendChild(section);
761
858
  }
762
- elm.appendChild(aside);
859
+ el.appendChild(aside);
860
+ return el;
763
861
  }
764
862
  function isInline(drawer) {
765
863
  return drawer.classList.contains("ai-context--inline");
@@ -805,6 +903,7 @@ function initAiContext(root) {
805
903
  void drawer.offsetWidth;
806
904
  drawer.classList.add("is-open");
807
905
  syncTriggers(drawer, true);
906
+ dispatch(drawer, "icen:ai-context-open", {});
808
907
  const closeBtn = drawer.querySelector("[data-ai-context-close]");
809
908
  (closeBtn ?? drawer).focus?.();
810
909
  }
@@ -812,6 +911,7 @@ function initAiContext(root) {
812
911
  if (drawer.hidden && !drawer.classList.contains("is-open")) return;
813
912
  drawer.classList.remove("is-open");
814
913
  syncTriggers(drawer, false);
914
+ dispatch(drawer, "icen:ai-context-close", {});
815
915
  const finish = () => {
816
916
  drawer.hidden = true;
817
917
  if (restoreFocus && lastTrigger && document.contains(lastTrigger)) {
@@ -943,55 +1043,55 @@ function buildAuditBody(body, entries, opts = {}) {
943
1043
  g.requests++;
944
1044
  groups.set(key, g);
945
1045
  }
946
- const totals = el("div", "ai-audit-totals");
1046
+ const totals = h("div", "ai-audit-totals");
947
1047
  const bits = [`${entries.length} \u6B21\u8BF7\u6C42`];
948
1048
  if (errors > 0) bits.push(`${errors} \u5931\u8D25`);
949
1049
  bits.push(`${formatTokens(tokens)} tok`);
950
1050
  if (cost > 0) bits.push(`$${(Math.round(cost * 1e6) / 1e6).toFixed(4)}`);
951
1051
  if (ttftN > 0) bits.push(`TTFT ${formatDuration(Math.round(ttftSum / ttftN))}`);
952
- totals.appendChild(el("span", "ai-audit-totals-text", bits.join(" \xB7 ")));
1052
+ totals.appendChild(h("span", "ai-audit-totals-text", bits.join(" \xB7 ")));
953
1053
  body.appendChild(totals);
954
1054
  const sorted = Array.from(groups.values()).sort((a, b) => b.tokens - a.tokens);
955
1055
  if (sorted.length > 0) {
956
- const list2 = el("div", "ai-audit-models");
1056
+ const list2 = h("div", "ai-audit-models");
957
1057
  for (const g of sorted) {
958
- const row = el("div", "ai-audit-model-row");
959
- row.appendChild(el("span", "ai-audit-model-name", g.key));
960
- row.appendChild(el("span", "ai-audit-model-meta", `${g.requests} \u6B21 \xB7 ${formatTokens(g.tokens)} tok${g.cost > 0 ? ` \xB7 $${g.cost.toFixed(4)}` : ""}`));
1058
+ const row = h("div", "ai-audit-model-row");
1059
+ row.appendChild(h("span", "ai-audit-model-name", g.key));
1060
+ row.appendChild(h("span", "ai-audit-model-meta", `${g.requests} \u6B21 \xB7 ${formatTokens(g.tokens)} tok${g.cost > 0 ? ` \xB7 $${g.cost.toFixed(4)}` : ""}`));
961
1061
  list2.appendChild(row);
962
1062
  }
963
1063
  body.appendChild(list2);
964
1064
  }
965
1065
  const recent = entries.slice(-limit).reverse();
966
- const list = el("div", "ai-audit-list");
1066
+ const list = h("div", "ai-audit-list");
967
1067
  for (const e of recent) {
968
- const item = el("div", `ai-audit-item is-${e.status === "ok" ? "done" : "error"}`);
969
- item.appendChild(el("span", "ai-audit-dot"));
970
- const main = el("span", "ai-audit-item-main");
971
- main.appendChild(el("span", "ai-audit-item-model", `${e.provider} \xB7 ${e.model}`));
1068
+ const item = h("div", `ai-audit-item is-${e.status === "ok" ? "done" : "error"}`);
1069
+ item.appendChild(h("span", "ai-audit-dot"));
1070
+ const main = h("span", "ai-audit-item-main");
1071
+ main.appendChild(h("span", "ai-audit-item-model", `${e.provider} \xB7 ${e.model}`));
972
1072
  const metaBits = [timeOf(e.ts), formatDuration(e.durationMs)];
973
1073
  if (typeof e.ttftMs === "number") metaBits.push(`TTFT ${formatDuration(e.ttftMs)}`);
974
1074
  if ((e.usage?.total ?? 0) > 0) metaBits.push(`${formatTokens(e.usage?.total ?? 0)} tok`);
975
- main.appendChild(el("span", "ai-audit-item-meta", metaBits.filter(Boolean).join(" \xB7 ")));
1075
+ main.appendChild(h("span", "ai-audit-item-meta", metaBits.filter(Boolean).join(" \xB7 ")));
976
1076
  item.appendChild(main);
977
- if (e.error) item.appendChild(el("span", "ai-audit-item-error", e.error));
1077
+ if (e.error) item.appendChild(h("span", "ai-audit-item-error", e.error));
978
1078
  list.appendChild(item);
979
1079
  }
980
1080
  body.appendChild(list);
981
1081
  }
982
- function renderAiAudit(elm, source, opts = {}) {
983
- if (typeof document === "undefined") return;
984
- elm.textContent = "";
985
- const root = el("div", "ai-audit");
1082
+ function renderAiAudit(el, source, opts = {}) {
1083
+ if (typeof document === "undefined") return el;
1084
+ el.textContent = "";
1085
+ const root = h("div", "ai-audit");
986
1086
  const entries = auditEntriesOf(source);
987
1087
  if (entries.length === 0) {
988
- root.appendChild(el("div", "ai-audit-empty", "\u6682\u65E0\u5BA1\u8BA1\u8BB0\u5F55"));
989
- elm.appendChild(root);
990
- return;
1088
+ root.appendChild(h("div", "ai-audit-empty", "\u6682\u65E0\u5BA1\u8BA1\u8BB0\u5F55"));
1089
+ el.appendChild(root);
1090
+ return el;
991
1091
  }
992
1092
  buildAuditBody(root, entries, { limit: opts.limit });
993
1093
  if (opts.clearButton !== false) {
994
- const clear = el("button", "btn btn-sm ai-audit-clear", "\u6E05\u9664\u5BA1\u8BA1");
1094
+ const clear = h("button", "btn btn-sm ai-audit-clear", "\u6E05\u9664\u5BA1\u8BA1");
995
1095
  clear.type = "button";
996
1096
  clear.addEventListener("click", () => {
997
1097
  if (opts.onClear) opts.onClear();
@@ -999,7 +1099,8 @@ function renderAiAudit(elm, source, opts = {}) {
999
1099
  });
1000
1100
  root.appendChild(clear);
1001
1101
  }
1002
- elm.appendChild(root);
1102
+ el.appendChild(root);
1103
+ return el;
1003
1104
  }
1004
1105
  export {
1005
1106
  initAiContext,