@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,6 @@
1
1
  type AiStatus = 'pending' | 'running' | 'streaming' | 'approval' | 'done' | 'error' | 'cancelled';
2
+ /** 状态机七态(规格 §1 的唯一语言)——集群共享(切 is-* 类 / 校验 wire status 用) */
3
+ declare const AI_STATUSES: readonly AiStatus[];
2
4
  /** 中文状态标签:等待/执行中/流式中/待确认/完成/失败/已取消 */
3
5
  declare function aiStatusLabel(s: AiStatus): string;
4
6
  interface AiUsage {
@@ -9,6 +11,8 @@ interface AiUsage {
9
11
  reasoning?: number;
10
12
  total?: number;
11
13
  }
14
+ /** 六键用量求和(非破坏性:不动入参,返回新对象);语义对齐 ai-provider 的同名私有实现,集群共用 */
15
+ declare function addUsage(sum: AiUsage, u: AiUsage): AiUsage;
12
16
  type AiKindTint = 'accent' | 'success' | 'warning' | 'error' | 'info' | 'muted';
13
17
  interface AiKindDef {
14
18
  /** 一行中文名(tool-call 卡片的标题区展示) */
@@ -39,7 +43,7 @@ interface AiToolCallModel {
39
43
  }
40
44
  /** 注册(或覆盖)一个 kind;name 大小写不敏感。第三方扩展入口。 */
41
45
  declare function registerAiKind(name: string, def: AiKindDef): void;
42
- /** 取 kind 定义;未注册回退 'note'。 */
46
+ /** 取 kind 定义;未注册回退内置 'note'。 */
43
47
  declare function getAiKind(name: string): AiKindDef;
44
48
  /** 从 tool 名推断 kind:Bash/Shell→shell、Read→read、mcp__*→mcp 等。 */
45
49
  declare function inferKind(toolName: string): string;
@@ -114,9 +118,8 @@ interface AiResourceLinkPart {
114
118
  type AiContentPart = AiTextPart | AiMediaPart | AiResourceLinkPart;
115
119
  /** 消息与工具回执的内容:字符串(纯文本捷径)或部件数组(多模态) */
116
120
  type AiContent = string | AiContentPart[];
121
+ /** 单部件守卫:type 合法且槽位类型正确(text 看 text、resource-link 看 uri、媒体看 url 或 data+mimeType) */
117
122
  declare function isAiContentPart(v: unknown): v is AiContentPart;
118
- /** 数组且至少一项像内容部件 → 视为部件数组(tool output 多模态探测用) */
119
- declare function isAiContentPartArray(v: unknown): v is AiContentPart[];
120
123
  /** 渲染地址归一:url 直取;data + mimeType 拼 data URI(base64) */
121
124
  declare function aiContentUrl(part: AiContentPart): string | undefined;
122
125
  /**
@@ -140,6 +143,8 @@ declare function estimateTokens(content: AiContent | AiUsage): number;
140
143
  * 本次输出会成为下轮输入的一部分。与 auditor.summary()(累计计费)语义不同。
141
144
  */
142
145
  declare function contextEstimate(usage: AiUsage): number;
146
+ /** 极简 DOM 构件:createElement + 可选 className + 可选 textContent —— 集群共用,免四处 createElement 三连 */
147
+ declare function h<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string, text?: string): HTMLElementTagNameMap[K];
143
148
  /**
144
149
  * 把内联 SVG 字符串解析为消毒后的 SVGElement:
145
150
  * - 剥全部 on* 事件属性;剔除 script / foreignObject 子树
@@ -148,4 +153,4 @@ declare function contextEstimate(usage: AiUsage): number;
148
153
  */
149
154
  declare function svgIcon(svg: string): SVGElement | null;
150
155
 
151
- export { type AiAudioPart, type AiContent, type AiContentPart, type AiFilePart, type AiImagePart, type AiKindDef, type AiKindTint, type AiMediaPart, type AiResourceLinkPart, type AiStatus, type AiTextPart, type AiToolCallModel, type AiUsage, type AiVideoPart, aiContentUrl, aiStatusLabel, contentToText, contextEstimate, estimateTokens, formatDuration, formatTokens, getAiKind, inferKind, isAiContentPart, isAiContentPartArray, normalizeContentParts, normalizeMcpContent, normalizeToolCall, normalizeUsage, registerAiKind, svgIcon };
156
+ export { AI_STATUSES, type AiAudioPart, type AiContent, type AiContentPart, type AiFilePart, type AiImagePart, type AiKindDef, type AiKindTint, type AiMediaPart, type AiResourceLinkPart, type AiStatus, type AiTextPart, type AiToolCallModel, type AiUsage, type AiVideoPart, addUsage, aiContentUrl, aiStatusLabel, contentToText, contextEstimate, estimateTokens, formatDuration, formatTokens, getAiKind, h, inferKind, isAiContentPart, normalizeContentParts, normalizeMcpContent, normalizeToolCall, normalizeUsage, registerAiKind, svgIcon };
@@ -23,6 +23,15 @@ function aiStatusLabel(s) {
23
23
  };
24
24
  return labels[s] ?? s;
25
25
  }
26
+ var USAGE_KEYS = ["input", "output", "cacheRead", "cacheWrite", "reasoning", "total"];
27
+ function addUsage(sum2, u) {
28
+ const out = { ...sum2 };
29
+ for (const k of USAGE_KEYS) {
30
+ const v = u[k];
31
+ if (typeof v === "number" && Number.isFinite(v)) out[k] = (out[k] ?? 0) + v;
32
+ }
33
+ return out;
34
+ }
26
35
  var svg = (body) => `<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" aria-hidden="true">${body}</svg>`;
27
36
  var ICON_TERMINAL = svg('<path d="m4 17 6-6-6-6"/><path d="M12 19h8"/>');
28
37
  var ICON_FILE = svg(
@@ -147,14 +156,13 @@ var BUILTIN_KINDS = {
147
156
  note: { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" },
148
157
  chart: { label: "\u56FE\u8868", icon: ICON_CHART, tint: "info", summarize: sum((i) => pickStr(i, ["title", "name"])) }
149
158
  };
150
- var FALLBACK_KIND = { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" };
151
159
  var kindRegistry = new Map(Object.entries(BUILTIN_KINDS));
152
160
  function registerAiKind(name, def) {
153
161
  const key = name.trim().toLowerCase();
154
162
  if (key) kindRegistry.set(key, def);
155
163
  }
156
164
  function getAiKind(name) {
157
- return kindRegistry.get(name.trim().toLowerCase()) ?? kindRegistry.get("note") ?? FALLBACK_KIND;
165
+ return kindRegistry.get(name.trim().toLowerCase()) ?? BUILTIN_KINDS.note;
158
166
  }
159
167
  var KIND_RULES = [
160
168
  [/^mcp__/, "mcp"],
@@ -280,9 +288,9 @@ function formatDuration(ms) {
280
288
  const m = Math.floor(ms / 6e4);
281
289
  const s = Math.round(ms % 6e4 / 1e3);
282
290
  if (ms < 36e5) return s > 0 ? `${m}m${s}s` : `${m}m`;
283
- const h = Math.floor(ms / 36e5);
291
+ const h2 = Math.floor(ms / 36e5);
284
292
  const rm = Math.round(ms % 36e5 / 6e4);
285
- return rm > 0 ? `${h}h${rm}m` : `${h}h`;
293
+ return rm > 0 ? `${h2}h${rm}m` : `${h2}h`;
286
294
  }
287
295
  var PART_TYPES = /* @__PURE__ */ new Set(["text", "image", "audio", "video", "file", "resource-link"]);
288
296
  function isAiContentPart(v) {
@@ -293,9 +301,6 @@ function isAiContentPart(v) {
293
301
  if (t === "resource-link") return typeof v.uri === "string";
294
302
  return typeof v.url === "string" || typeof v.data === "string" && typeof v.mimeType === "string";
295
303
  }
296
- function isAiContentPartArray(v) {
297
- return Array.isArray(v) && v.length > 0 && v.every(isAiContentPart);
298
- }
299
304
  function aiContentUrl(part) {
300
305
  if (part.type === "text" || part.type === "resource-link") return void 0;
301
306
  if (typeof part.url === "string" && part.url) return part.url;
@@ -438,6 +443,12 @@ function contextEstimate(usage) {
438
443
  const u = normalizeUsage(usage);
439
444
  return (u.input ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0) + (u.output ?? 0) + (u.reasoning ?? 0);
440
445
  }
446
+ function h(tag, className, text) {
447
+ const node = document.createElement(tag);
448
+ if (className) node.className = className;
449
+ if (text != null) node.textContent = text;
450
+ return node;
451
+ }
441
452
  function svgIcon(svg2) {
442
453
  if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
443
454
  let parsed;
@@ -471,6 +482,8 @@ function svgIcon(svg2) {
471
482
  return document.importNode(root, true);
472
483
  }
473
484
  export {
485
+ AI_STATUSES,
486
+ addUsage,
474
487
  aiContentUrl,
475
488
  aiStatusLabel,
476
489
  contentToText,
@@ -479,9 +492,9 @@ export {
479
492
  formatDuration,
480
493
  formatTokens,
481
494
  getAiKind,
495
+ h,
482
496
  inferKind,
483
497
  isAiContentPart,
484
- isAiContentPartArray,
485
498
  normalizeContentParts,
486
499
  normalizeMcpContent,
487
500
  normalizeToolCall,
@@ -40,10 +40,10 @@ interface AiDiffModel {
40
40
  */
41
41
  declare function parseUnifiedDiff(text: string): AiDiffFile[];
42
42
  /**
43
- * 渲染 diff 审阅卡(DOM API,全 textContent)。
43
+ * 渲染 diff 审阅卡(DOM API,全 textContent),返回挂载元素 el(render* 约定)。
44
44
  * 接受 parseUnifiedDiff 的产物;重复调用会替换 el 内容。
45
45
  */
46
- declare function renderAiDiff(elm: HTMLElement, model: AiDiffModel): void;
46
+ declare function renderAiDiff(el: HTMLElement, model: AiDiffModel): HTMLElement;
47
47
  /**
48
48
  * 委托初始化:展开/折叠 + 接受/拒绝。重复调用安全;
49
49
  * 返回销毁函数(移除监听并复位标记,可重新 init)。
@@ -1,3 +1,143 @@
1
+ // src/behaviors/ai-core.ts
2
+ var svg = (body) => `<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" aria-hidden="true">${body}</svg>`;
3
+ var ICON_TERMINAL = svg('<path d="m4 17 6-6-6-6"/><path d="M12 19h8"/>');
4
+ var ICON_FILE = svg(
5
+ '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>'
6
+ );
7
+ var ICON_FILE_PEN = svg(
8
+ '<path d="M12.5 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8l5 5v3.5"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M19 6.5 16.5 4"/><path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/>'
9
+ );
10
+ var ICON_FILE_PLUS = svg(
11
+ '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M9 15h6"/><path d="M12 12v6"/>'
12
+ );
13
+ var ICON_TRASH = svg(
14
+ '<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6"/><path d="M14 11v6"/>'
15
+ );
16
+ var ICON_SEARCH = svg('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>');
17
+ var ICON_FOLDER_SEARCH = svg(
18
+ '<path d="M11 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v4"/><circle cx="17" cy="17" r="3"/><path d="m21 21-1.9-1.9"/>'
19
+ );
20
+ var ICON_SCAN_SEARCH = svg(
21
+ '<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/>'
22
+ );
23
+ var ICON_GLOBE = svg(
24
+ '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>'
25
+ );
26
+ var ICON_DOWNLOAD = svg(
27
+ '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>'
28
+ );
29
+ var ICON_BLOCKS = svg(
30
+ '<rect width="7" height="7" x="4" y="4" rx="1"/><rect width="7" height="7" x="13" y="4" rx="1"/><rect width="7" height="7" x="4" y="13" rx="1"/><rect width="7" height="7" x="13" y="13" rx="1"/>'
31
+ );
32
+ var ICON_ZAP = svg(
33
+ '<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>'
34
+ );
35
+ var ICON_LIST = svg(
36
+ '<path d="M3 12h.01"/><path d="M3 18h.01"/><path d="M3 6h.01"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M8 6h13"/>'
37
+ );
38
+ var ICON_LIST_CHECKS = svg(
39
+ '<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/>'
40
+ );
41
+ var ICON_BOT = svg(
42
+ '<path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/>'
43
+ );
44
+ var ICON_CHART = svg(
45
+ '<path d="M3 3v18h18"/><path d="M7 15l4-6 3 4 5-8"/>'
46
+ );
47
+ var ICON_FILE_TEXT = svg(
48
+ '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>'
49
+ );
50
+ function isObj(v) {
51
+ return typeof v === "object" && v !== null;
52
+ }
53
+ function pickStr(input, keys) {
54
+ if (!isObj(input)) return void 0;
55
+ for (const k of keys) {
56
+ const v = input[k];
57
+ if (typeof v === "string" && v) return v;
58
+ }
59
+ return void 0;
60
+ }
61
+ function pickNum(src, keys) {
62
+ if (!isObj(src)) return void 0;
63
+ for (const k of keys) {
64
+ const v = src[k];
65
+ if (typeof v === "number" && Number.isFinite(v)) return v;
66
+ }
67
+ return void 0;
68
+ }
69
+ function firstLine(s, max = 80) {
70
+ const line = s.split("\n", 1)[0]?.trim() ?? "";
71
+ return line.length > max ? `${line.slice(0, max)}\u2026` : line;
72
+ }
73
+ function filePathOf(input) {
74
+ return pickStr(input, ["file_path", "path", "file", "filename"]);
75
+ }
76
+ var summarizePath = (input) => filePathOf(input);
77
+ function summarizeRead(input) {
78
+ const p = filePathOf(input);
79
+ if (!p) return void 0;
80
+ const offset = pickNum(input, ["offset", "start_line"]);
81
+ if (offset == null) return p;
82
+ const limit = pickNum(input, ["limit", "end_line"]);
83
+ return limit != null ? `${p}:${offset}-${offset + limit}` : `${p}:${offset}`;
84
+ }
85
+ function summarizeShell(input) {
86
+ const cmd = typeof input === "string" ? input : pickStr(input, ["command", "cmd", "script"]);
87
+ return cmd ? firstLine(cmd) : void 0;
88
+ }
89
+ function summarizeGrep(input) {
90
+ const pattern = pickStr(input, ["pattern", "regex", "query"]);
91
+ const p = filePathOf(input);
92
+ if (pattern) return p ? `${firstLine(pattern, 40)} \xB7 ${p}` : firstLine(pattern);
93
+ return p;
94
+ }
95
+ var sum = (fn) => {
96
+ return (input) => fn(input) ?? "";
97
+ };
98
+ var BUILTIN_KINDS = {
99
+ shell: { label: "\u547D\u4EE4", icon: ICON_TERMINAL, tint: "accent", summarize: sum(summarizeShell) },
100
+ read: { label: "\u8BFB\u53D6", icon: ICON_FILE, tint: "info", summarize: sum(summarizeRead) },
101
+ edit: { label: "\u7F16\u8F91", icon: ICON_FILE_PEN, tint: "warning", summarize: sum(summarizePath) },
102
+ write: { label: "\u5199\u5165", icon: ICON_FILE_PLUS, tint: "warning", summarize: sum(summarizePath) },
103
+ rm: { label: "\u5220\u9664", icon: ICON_TRASH, tint: "error", summarize: sum(summarizePath) },
104
+ grep: { label: "\u641C\u7D22", icon: ICON_SEARCH, tint: "info", summarize: sum(summarizeGrep) },
105
+ glob: { label: "\u5339\u914D", icon: ICON_FOLDER_SEARCH, tint: "info", summarize: sum((i) => pickStr(i, ["pattern", "glob", "path"])) },
106
+ browser: { label: "\u6D4F\u89C8", icon: ICON_GLOBE, tint: "accent", summarize: sum((i) => pickStr(i, ["url", "uri", "target"])) },
107
+ search: { label: "\u68C0\u7D22", icon: ICON_SCAN_SEARCH, tint: "accent", summarize: sum((i) => pickStr(i, ["query", "q", "pattern"])) },
108
+ fetch: { label: "\u8BF7\u6C42", icon: ICON_DOWNLOAD, tint: "info", summarize: sum((i) => pickStr(i, ["url", "uri", "endpoint"])) },
109
+ mcp: { label: "MCP", icon: ICON_BLOCKS, tint: "muted", summarize: sum((i) => pickStr(i, ["tool", "method", "name"])) },
110
+ skill: { label: "\u6280\u80FD", icon: ICON_ZAP, tint: "accent", summarize: sum((i) => pickStr(i, ["skill", "name", "command"])) },
111
+ todo: {
112
+ label: "\u5F85\u529E",
113
+ icon: ICON_LIST,
114
+ tint: "info",
115
+ summarize: sum((input) => {
116
+ const items = input && typeof input === "object" && Array.isArray(input.items) ? input.items : Array.isArray(input) ? input : [];
117
+ const done = items.filter((it) => it?.status === "done").length;
118
+ return items.length > 0 ? `${done}/${items.length} \u5B8C\u6210` : "";
119
+ })
120
+ },
121
+ plan: { label: "\u8BA1\u5212", icon: ICON_LIST_CHECKS, tint: "info" },
122
+ subagent: { label: "\u5B50\u4EE3\u7406", icon: ICON_BOT, tint: "accent", summarize: sum((i) => pickStr(i, ["task", "description", "prompt", "message", "subagent_type"])) },
123
+ note: { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" },
124
+ chart: { label: "\u56FE\u8868", icon: ICON_CHART, tint: "info", summarize: sum((i) => pickStr(i, ["title", "name"])) }
125
+ };
126
+ var kindRegistry = new Map(Object.entries(BUILTIN_KINDS));
127
+ function h(tag, className, text) {
128
+ const node = document.createElement(tag);
129
+ if (className) node.className = className;
130
+ if (text != null) node.textContent = text;
131
+ return node;
132
+ }
133
+
134
+ // src/behaviors/events.ts
135
+ function emitIcen(source, type, detail) {
136
+ return source.dispatchEvent(
137
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
138
+ );
139
+ }
140
+
1
141
  // src/behaviors/ai-diff.ts
2
142
  var RE_HUNK = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
3
143
  var RE_OLD_FILE = /^--- (.*)$/;
@@ -114,60 +254,54 @@ function parseUnifiedDiff(text) {
114
254
  }
115
255
  return files;
116
256
  }
117
- function el(tag, className, text) {
118
- const node = document.createElement(tag);
119
- if (className) node.className = className;
120
- if (text !== void 0) node.textContent = text;
121
- return node;
122
- }
123
257
  function renderLineRow(line) {
124
258
  const tr = document.createElement("tr");
125
259
  tr.className = `ai-diff-line--${line.type}`;
126
260
  if (line.type === "hunk") {
127
- const td = el("td", "", line.text);
261
+ const td = h("td", "", line.text);
128
262
  td.colSpan = 3;
129
263
  tr.appendChild(td);
130
264
  return tr;
131
265
  }
132
- const oldTd = el("td", "ai-diff-ln", line.oldNo === null ? "" : String(line.oldNo));
266
+ const oldTd = h("td", "ai-diff-ln", line.oldNo === null ? "" : String(line.oldNo));
133
267
  oldTd.setAttribute("aria-hidden", "true");
134
- const newTd = el("td", "ai-diff-ln", line.newNo === null ? "" : String(line.newNo));
268
+ const newTd = h("td", "ai-diff-ln", line.newNo === null ? "" : String(line.newNo));
135
269
  newTd.setAttribute("aria-hidden", "true");
136
270
  tr.appendChild(oldTd);
137
271
  tr.appendChild(newTd);
138
- tr.appendChild(el("td", "ai-diff-code", line.text));
272
+ tr.appendChild(h("td", "ai-diff-code", line.text));
139
273
  return tr;
140
274
  }
141
275
  function renderFile(file) {
142
- const root = el("div", `ai-diff-file is-${file.status}`);
276
+ const root = h("div", `ai-diff-file is-${file.status}`);
143
277
  root.dataset.path = file.path;
144
- const head = el("div", "ai-diff-head");
278
+ const head = h("div", "ai-diff-head");
145
279
  head.setAttribute("role", "button");
146
280
  head.tabIndex = 0;
147
281
  head.setAttribute("aria-expanded", "false");
148
282
  const pathText = file.status === "renamed" && file.oldPath ? `${file.oldPath} \u2192 ${file.path}` : file.path;
149
- head.appendChild(el("span", "ai-diff-path", pathText));
150
- const stat = el("span", "ai-diff-stat");
151
- if (file.addCount > 0) stat.appendChild(el("b", "ai-diff-add", `+${file.addCount}`));
152
- if (file.delCount > 0) stat.appendChild(el("b", "ai-diff-del", `\u2212${file.delCount}`));
283
+ head.appendChild(h("span", "ai-diff-path", pathText));
284
+ const stat = h("span", "ai-diff-stat");
285
+ if (file.addCount > 0) stat.appendChild(h("b", "ai-diff-add", `+${file.addCount}`));
286
+ if (file.delCount > 0) stat.appendChild(h("b", "ai-diff-del", `\u2212${file.delCount}`));
153
287
  if (stat.children.length > 0) head.appendChild(stat);
154
- const actions = el("span", "ai-diff-actions");
155
- actions.appendChild(el("button", "", "\u63A5\u53D7")).setAttribute("data-ai-diff-accept", "");
156
- const rejectBtn = el("button", "", "\u62D2\u7EDD");
288
+ const actions = h("span", "ai-diff-actions");
289
+ actions.appendChild(h("button", "", "\u63A5\u53D7")).setAttribute("data-ai-diff-accept", "");
290
+ const rejectBtn = h("button", "", "\u62D2\u7EDD");
157
291
  rejectBtn.setAttribute("data-ai-diff-reject", "");
158
292
  actions.appendChild(rejectBtn);
159
293
  head.appendChild(actions);
160
- const verdict = el("span", "ai-diff-verdict");
294
+ const verdict = h("span", "ai-diff-verdict");
161
295
  verdict.hidden = true;
162
296
  head.appendChild(verdict);
163
297
  root.appendChild(head);
164
- const body = el("div", "ai-diff-body");
298
+ const body = h("div", "ai-diff-body");
165
299
  body.hidden = true;
166
- const table = el("table", "ai-diff-table");
300
+ const table = h("table", "ai-diff-table");
167
301
  if (file.hunks.length === 0) {
168
302
  const tr = document.createElement("tr");
169
303
  tr.className = "ai-diff-line--ctx";
170
- const td = el("td", "", file.binary ? "\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u663E\u793A\u6587\u672C\u5DEE\u5F02" : "\u65E0\u5DEE\u5F02\u5185\u5BB9");
304
+ const td = h("td", "", file.binary ? "\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u663E\u793A\u6587\u672C\u5DEE\u5F02" : "\u65E0\u5DEE\u5F02\u5185\u5BB9");
171
305
  td.colSpan = 3;
172
306
  tr.appendChild(td);
173
307
  table.appendChild(tr);
@@ -181,13 +315,14 @@ function renderFile(file) {
181
315
  root.appendChild(body);
182
316
  return root;
183
317
  }
184
- function renderAiDiff(elm, model) {
185
- if (typeof document === "undefined") return;
186
- elm.textContent = "";
187
- const root = el("div", "ai-diff");
318
+ function renderAiDiff(el, model) {
319
+ if (typeof document === "undefined") return el;
320
+ el.textContent = "";
321
+ const root = h("div", "ai-diff");
188
322
  const files = Array.isArray(model?.files) ? model.files : [];
189
323
  for (const file of files) root.appendChild(renderFile(file));
190
- elm.appendChild(root);
324
+ el.appendChild(root);
325
+ return el;
191
326
  }
192
327
  var noopDestroy = () => void 0;
193
328
  function closestFile(node) {
@@ -206,10 +341,7 @@ function toggleFile(head) {
206
341
  const open = body.hidden;
207
342
  body.hidden = !open;
208
343
  head.setAttribute("aria-expanded", String(open));
209
- file.dispatchEvent(new CustomEvent("icen:ai-toggle", {
210
- bubbles: true,
211
- detail: { el: file, open }
212
- }));
344
+ emitIcen(file, "icen:ai-toggle", { el: file, open });
213
345
  }
214
346
  function decide(btn, accepted) {
215
347
  const file = closestFile(btn);
@@ -218,7 +350,7 @@ function decide(btn, accepted) {
218
350
  file.classList.add(accepted ? "is-accepted" : "is-rejected");
219
351
  let verdict = file.querySelector(".ai-diff-verdict");
220
352
  if (!verdict) {
221
- verdict = el("span", "ai-diff-verdict");
353
+ verdict = h("span", "ai-diff-verdict");
222
354
  file.querySelector(".ai-diff-head")?.appendChild(verdict);
223
355
  }
224
356
  verdict.textContent = accepted ? "\u5DF2\u63A5\u53D7" : "\u5DF2\u62D2\u7EDD";
@@ -229,10 +361,7 @@ function decide(btn, accepted) {
229
361
  b.hidden = true;
230
362
  b.disabled = true;
231
363
  });
232
- file.dispatchEvent(new CustomEvent(accepted ? "icen:ai-diff-accept" : "icen:ai-diff-reject", {
233
- bubbles: true,
234
- detail: { path: filePath(file) }
235
- }));
364
+ emitIcen(file, accepted ? "icen:ai-diff-accept" : "icen:ai-diff-reject", { path: filePath(file) });
236
365
  }
237
366
  function initAiDiff(root) {
238
367
  if (typeof document === "undefined") return noopDestroy;
@@ -244,14 +373,16 @@ function initAiDiff(root) {
244
373
  const onClick = (e) => {
245
374
  const t = e.target instanceof Element ? e.target : null;
246
375
  if (!t) return;
247
- if (t.closest("[data-ai-diff-accept]")) {
376
+ const acceptBtn = t.closest("[data-ai-diff-accept]");
377
+ if (acceptBtn) {
248
378
  e.stopPropagation();
249
- decide(t.closest("[data-ai-diff-accept]"), true);
379
+ decide(acceptBtn, true);
250
380
  return;
251
381
  }
252
- if (t.closest("[data-ai-diff-reject]")) {
382
+ const rejectBtn = t.closest("[data-ai-diff-reject]");
383
+ if (rejectBtn) {
253
384
  e.stopPropagation();
254
- decide(t.closest("[data-ai-diff-reject]"), false);
385
+ decide(rejectBtn, false);
255
386
  return;
256
387
  }
257
388
  const head = t.closest(".ai-diff-head");
@@ -0,0 +1,26 @@
1
+ /** 反馈事件 detail(与 IcenEventMap['icen:ai-feedback'] 同构;el 为 .ai-feedback 根) */
2
+ interface AiFeedbackDetail {
3
+ value: 1 | -1 | 0;
4
+ reason?: string;
5
+ el?: HTMLElement;
6
+ }
7
+ interface AiFeedbackOpts {
8
+ /** 踩因枚举(默认五项) */
9
+ reasons?: string[];
10
+ /** 反馈回调(与 icen:ai-feedback 事件双通道) */
11
+ onFeedback?: (detail: AiFeedbackDetail) => void;
12
+ }
13
+ /**
14
+ * 渲染 👍/👎 双钮 + 踩因 chips + 提交/已提交态(快照渲染,全 textContent)。
15
+ * 交互由 initAiFeedback 委托驱动;opts.onFeedback 与 icen:ai-feedback 事件双通道。
16
+ * 返回挂载容器 el(render* 返回挂载元素约定,SSR 下原样返回)。
17
+ */
18
+ declare function renderAiFeedback(el: HTMLElement, opts?: AiFeedbackOpts): HTMLElement;
19
+ /**
20
+ * 反馈委托初始化:vote 切换/取消、chip 单选、提交进入 .is-done 终态。
21
+ * 幂等(scope 挂 __icenAiFeedbackInit);返回销毁函数(解绑监听 + 复位标记,可重新 init)。
22
+ * SSR 下为 no-op。按钮为原生 button,键盘 Enter/Space 走原生 click,无需键盘委托。
23
+ */
24
+ declare function initAiFeedback(root?: ParentNode): () => void;
25
+
26
+ export { type AiFeedbackDetail, type AiFeedbackOpts, initAiFeedback, renderAiFeedback };