@icen.ai/ui 0.8.1 → 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 (141) hide show
  1. package/AGENTS.md +49 -6
  2. package/README.md +12 -9
  3. package/dist/behaviors/ai-branch.d.mts +19 -0
  4. package/dist/behaviors/ai-branch.mjs +217 -0
  5. package/dist/behaviors/ai-chat.d.mts +8 -6
  6. package/dist/behaviors/ai-chat.mjs +68 -8
  7. package/dist/behaviors/ai-composer.mjs +108 -20
  8. package/dist/behaviors/ai-feedback.d.mts +26 -0
  9. package/dist/behaviors/ai-feedback.mjs +303 -0
  10. package/dist/behaviors/ai-panel.d.mts +15 -1
  11. package/dist/behaviors/ai-panel.mjs +26 -0
  12. package/dist/behaviors/ai-threads.d.mts +38 -0
  13. package/dist/behaviors/ai-threads.mjs +319 -0
  14. package/dist/behaviors/ai-tool.mjs +5 -0
  15. package/dist/behaviors/ai-tools.mjs +5 -0
  16. package/dist/behaviors/charts.d.mts +103 -1
  17. package/dist/behaviors/charts.mjs +795 -0
  18. package/dist/behaviors/events.d.mts +193 -0
  19. package/dist/behaviors/kb-access.d.mts +34 -0
  20. package/dist/behaviors/kb-access.mjs +569 -0
  21. package/dist/behaviors/kb-acl.d.mts +50 -0
  22. package/dist/behaviors/kb-acl.mjs +405 -0
  23. package/dist/behaviors/kb-answer.d.mts +38 -0
  24. package/dist/behaviors/kb-answer.mjs +2055 -0
  25. package/dist/behaviors/kb-audit.d.mts +23 -0
  26. package/dist/behaviors/kb-audit.mjs +375 -0
  27. package/dist/behaviors/kb-canvas.d.mts +44 -0
  28. package/dist/behaviors/kb-canvas.mjs +589 -0
  29. package/dist/behaviors/kb-chain.d.mts +15 -0
  30. package/dist/behaviors/kb-chain.mjs +270 -0
  31. package/dist/behaviors/kb-checkpoint.d.mts +16 -0
  32. package/dist/behaviors/kb-checkpoint.mjs +342 -0
  33. package/dist/behaviors/kb-chunks.d.mts +31 -0
  34. package/dist/behaviors/kb-chunks.mjs +475 -0
  35. package/dist/behaviors/kb-citation.d.mts +38 -0
  36. package/dist/behaviors/kb-citation.mjs +677 -0
  37. package/dist/behaviors/kb-clarify.d.mts +20 -0
  38. package/dist/behaviors/kb-clarify.mjs +248 -0
  39. package/dist/behaviors/kb-conflict.d.mts +22 -0
  40. package/dist/behaviors/kb-conflict.mjs +284 -0
  41. package/dist/behaviors/kb-connector.d.mts +17 -0
  42. package/dist/behaviors/kb-connector.mjs +375 -0
  43. package/dist/behaviors/kb-core.d.mts +529 -0
  44. package/dist/behaviors/kb-core.mjs +890 -0
  45. package/dist/behaviors/kb-eval.d.mts +22 -0
  46. package/dist/behaviors/kb-eval.mjs +408 -0
  47. package/dist/behaviors/kb-explain.d.mts +11 -0
  48. package/dist/behaviors/kb-explain.mjs +243 -0
  49. package/dist/behaviors/kb-filter.d.mts +31 -0
  50. package/dist/behaviors/kb-filter.mjs +587 -0
  51. package/dist/behaviors/kb-gap.d.mts +27 -0
  52. package/dist/behaviors/kb-gap.mjs +273 -0
  53. package/dist/behaviors/kb-hittest.d.mts +34 -0
  54. package/dist/behaviors/kb-hittest.mjs +409 -0
  55. package/dist/behaviors/kb-metadata.d.mts +29 -0
  56. package/dist/behaviors/kb-metadata.mjs +326 -0
  57. package/dist/behaviors/kb-passage.d.mts +41 -0
  58. package/dist/behaviors/kb-passage.mjs +529 -0
  59. package/dist/behaviors/kb-pipeline.d.mts +18 -0
  60. package/dist/behaviors/kb-pipeline.mjs +381 -0
  61. package/dist/behaviors/kb-qa.d.mts +26 -0
  62. package/dist/behaviors/kb-qa.mjs +332 -0
  63. package/dist/behaviors/kb-rerank.d.mts +20 -0
  64. package/dist/behaviors/kb-rerank.mjs +315 -0
  65. package/dist/behaviors/kb-retrieval.d.mts +49 -0
  66. package/dist/behaviors/kb-retrieval.mjs +746 -0
  67. package/dist/behaviors/kb-review.d.mts +44 -0
  68. package/dist/behaviors/kb-review.mjs +451 -0
  69. package/dist/behaviors/kb-sandbox.d.mts +33 -0
  70. package/dist/behaviors/kb-sandbox.mjs +325 -0
  71. package/dist/behaviors/kb-segment.d.mts +32 -0
  72. package/dist/behaviors/kb-segment.mjs +499 -0
  73. package/dist/behaviors/kb-sources.d.mts +27 -0
  74. package/dist/behaviors/kb-sources.mjs +373 -0
  75. package/dist/behaviors/kb-sql.d.mts +43 -0
  76. package/dist/behaviors/kb-sql.mjs +948 -0
  77. package/dist/behaviors/kb-trace.d.mts +16 -0
  78. package/dist/behaviors/kb-trace.mjs +414 -0
  79. package/dist/behaviors/kb-visibility.d.mts +44 -0
  80. package/dist/behaviors/kb-visibility.mjs +403 -0
  81. package/dist/behaviors/kb-who-can.d.mts +32 -0
  82. package/dist/behaviors/kb-who-can.mjs +379 -0
  83. package/dist/components/ai-branch.mjs +3 -0
  84. package/dist/components/ai-chat.css +295 -0
  85. package/dist/components/ai-feedback.mjs +3 -0
  86. package/dist/components/ai-panel.css +217 -0
  87. package/dist/components/ai-threads.mjs +3 -0
  88. package/dist/components/chart-graph.css +57 -0
  89. package/dist/components/chart-graph.mjs +4 -0
  90. package/dist/components/chart-map.mjs +3 -0
  91. package/dist/components/chart-scatter.mjs +1 -1
  92. package/dist/components/charts.css +32 -0
  93. package/dist/components/charts.mjs +1 -0
  94. package/dist/components/kb-access.mjs +4 -0
  95. package/dist/components/kb-acl.mjs +4 -0
  96. package/dist/components/kb-agent.css +481 -0
  97. package/dist/components/kb-answer.mjs +4 -0
  98. package/dist/components/kb-audit.mjs +4 -0
  99. package/dist/components/kb-canvas.mjs +4 -0
  100. package/dist/components/kb-chain.mjs +4 -0
  101. package/dist/components/kb-checkpoint.mjs +4 -0
  102. package/dist/components/kb-chunks.mjs +4 -0
  103. package/dist/components/kb-citation.mjs +4 -0
  104. package/dist/components/kb-clarify.mjs +4 -0
  105. package/dist/components/kb-conflict.mjs +4 -0
  106. package/dist/components/kb-connector.mjs +4 -0
  107. package/dist/components/kb-data.css +649 -0
  108. package/dist/components/kb-eval.mjs +4 -0
  109. package/dist/components/kb-explain.mjs +4 -0
  110. package/dist/components/kb-filter.mjs +4 -0
  111. package/dist/components/kb-gap.mjs +4 -0
  112. package/dist/components/kb-ground.css +566 -0
  113. package/dist/components/kb-hittest.mjs +4 -0
  114. package/dist/components/kb-ingest.css +898 -0
  115. package/dist/components/kb-metadata.mjs +4 -0
  116. package/dist/components/kb-ops.css +548 -0
  117. package/dist/components/kb-passage.mjs +4 -0
  118. package/dist/components/kb-perm.css +444 -0
  119. package/dist/components/kb-pipeline.mjs +4 -0
  120. package/dist/components/kb-qa.mjs +4 -0
  121. package/dist/components/kb-rerank.mjs +4 -0
  122. package/dist/components/kb-retrieval.mjs +4 -0
  123. package/dist/components/kb-review.mjs +4 -0
  124. package/dist/components/kb-sandbox.mjs +4 -0
  125. package/dist/components/kb-search.css +940 -0
  126. package/dist/components/kb-segment.mjs +4 -0
  127. package/dist/components/kb-sources.mjs +4 -0
  128. package/dist/components/kb-sql.mjs +4 -0
  129. package/dist/components/kb-trace.mjs +4 -0
  130. package/dist/components/kb-visibility.mjs +4 -0
  131. package/dist/components/kb-who-can.mjs +4 -0
  132. package/dist/components/kb.css +252 -0
  133. package/dist/components/kb.mjs +9 -0
  134. package/dist/index.d.mts +37 -2
  135. package/dist/index.mjs +8555 -205
  136. package/dist/registry.json +325 -3
  137. package/dist/ui.css +5451 -45
  138. package/docs/research/2026-10-03-ai-kb-components.md +392 -0
  139. package/docs/research/2026-10-03-kb-permissions.md +992 -0
  140. package/docs/spec/kb-family.md +443 -0
  141. package/package.json +2 -2
@@ -474,10 +474,16 @@ function buildTodoHead(done, total) {
474
474
  head.appendChild(bar);
475
475
  return head;
476
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>';
477
478
  function buildTodoItem(item, index) {
478
479
  const row = h("div", `ai-todo-item is-${item.status}`);
479
480
  row.dataset.index = String(index);
480
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);
481
487
  row.appendChild(h("span", "ai-todo-text", item.content));
482
488
  row.appendChild(h("span", "ai-todo-active", item.activeForm ?? item.content));
483
489
  return row;
@@ -581,6 +587,18 @@ function initAiTodo(root) {
581
587
  marked.__icenAiTodoInit = false;
582
588
  };
583
589
  }
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
+ }
584
602
  function renderAiUsage(el, usage, opts = {}) {
585
603
  if (typeof document === "undefined") return el;
586
604
  const u = normalizeUsage(usage);
@@ -621,6 +639,14 @@ function renderAiUsage(el, usage, opts = {}) {
621
639
  }
622
640
  root.appendChild(legend);
623
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 ")));
624
650
  const totalParts = [formatTokens(total)];
625
651
  if (typeof opts.total === "number" && opts.total > 0) {
626
652
  const pct = Math.min(100, Math.round(total / opts.total * 100));
@@ -0,0 +1,38 @@
1
+ interface AiThreadItem {
2
+ /** 稳定标识(事件 detail 的 key) */
3
+ key: string;
4
+ /** 会话标题 */
5
+ label: string;
6
+ /** 分组名(如「今天」/「昨天」/「更早」);groups 模式下生效 */
7
+ group?: string;
8
+ /** 内联 SVG 字符串(lucide 风格 24×24 stroke);渲染时经 svgIcon() 消毒 */
9
+ icon?: string;
10
+ /** 已归档:.is-archived 降透明并沉底(分组模式收进「已归档」组) */
11
+ archived?: boolean;
12
+ }
13
+ interface AiThreadsModel {
14
+ items: AiThreadItem[];
15
+ /** 当前激活会话 key(路由同步) */
16
+ activeKey?: string;
17
+ /** 分组模式:true 时按 group 字段分组(组头可折叠);缺省/false 平铺 */
18
+ groups?: boolean;
19
+ }
20
+ /**
21
+ * 快照渲染会话列表:平铺或分组(model.groups)、activeKey 高亮、archived 沉底
22
+ * (分组模式收进「已归档」组,组头可折叠)。返回挂载容器 el(SSR 原样返回)。
23
+ */
24
+ declare function renderAiThreads(el: HTMLElement, model: AiThreadsModel): HTMLElement;
25
+ /**
26
+ * 外部路由同步激活态:el 范围内 data-key === key 的 .ai-thread 挂 .is-active
27
+ * (+aria-current),其余摘除;key 为空串/null/undefined 时清空全部激活。
28
+ * 对静态手写 DOM 与 renderAiThreads 产物一视同仁。返回 el。
29
+ */
30
+ declare function setAiThreadsActive(el: HTMLElement, key: string | null | undefined): HTMLElement;
31
+ /**
32
+ * 会话列表委托初始化:选择 / 动作挂载点 / 组头折叠。
33
+ * 幂等(scope 挂 __icenAiThreadsInit);返回销毁函数(解绑监听 + 复位标记,可重新 init)。
34
+ * SSR 下为 no-op。
35
+ */
36
+ declare function initAiThreads(root?: ParentNode): () => void;
37
+
38
+ export { type AiThreadItem, type AiThreadsModel, initAiThreads, renderAiThreads, setAiThreadsActive };
@@ -0,0 +1,319 @@
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
+ function svgIcon(svg2) {
134
+ if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
135
+ let parsed;
136
+ try {
137
+ parsed = new DOMParser().parseFromString(svg2, "image/svg+xml");
138
+ } catch {
139
+ return null;
140
+ }
141
+ const root = parsed.documentElement;
142
+ if (!root || root.tagName.toLowerCase() !== "svg" || parsed.querySelector("parsererror")) {
143
+ return null;
144
+ }
145
+ const sanitize = (el) => {
146
+ for (const attr of Array.from(el.attributes)) {
147
+ const name = attr.name.toLowerCase();
148
+ if (name.startsWith("on")) el.removeAttribute(attr.name);
149
+ if ((name === "href" || name === "xlink:href") && attr.value.trim().toLowerCase().startsWith("javascript:")) {
150
+ el.removeAttribute(attr.name);
151
+ }
152
+ }
153
+ for (const child of Array.from(el.children)) {
154
+ const tag = child.tagName.toLowerCase();
155
+ if (tag === "script" || tag === "foreignobject") {
156
+ child.remove();
157
+ continue;
158
+ }
159
+ sanitize(child);
160
+ }
161
+ };
162
+ sanitize(root);
163
+ return document.importNode(root, true);
164
+ }
165
+
166
+ // src/behaviors/events.ts
167
+ function emitIcen(source, type, detail) {
168
+ return source.dispatchEvent(
169
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
170
+ );
171
+ }
172
+
173
+ // src/behaviors/ai-threads.ts
174
+ var ARCHIVED_GROUP = "\u5DF2\u5F52\u6863";
175
+ function dispatch(node, name, detail) {
176
+ emitIcen(node, name, detail);
177
+ }
178
+ function buildThread(item, activeKey) {
179
+ const row = h("div", "ai-thread");
180
+ if (item.key) row.dataset.key = item.key;
181
+ if (item.archived) row.classList.add("is-archived");
182
+ if (activeKey != null && item.key === activeKey) {
183
+ row.classList.add("is-active");
184
+ row.setAttribute("aria-current", "true");
185
+ }
186
+ row.setAttribute("role", "button");
187
+ row.tabIndex = 0;
188
+ if (item.icon) {
189
+ const iconWrap = h("span", "ai-thread-icon");
190
+ const svg2 = svgIcon(item.icon);
191
+ if (svg2) iconWrap.appendChild(svg2);
192
+ row.appendChild(iconWrap);
193
+ }
194
+ row.appendChild(h("span", "ai-thread-label", item.label));
195
+ const action = h("button", "ai-thread-action", "\xB7\xB7\xB7");
196
+ action.type = "button";
197
+ action.setAttribute("aria-label", "\u4F1A\u8BDD\u64CD\u4F5C");
198
+ action.setAttribute("data-thread-action", "rename");
199
+ row.appendChild(action);
200
+ return row;
201
+ }
202
+ function renderAiThreads(el, model) {
203
+ if (typeof document === "undefined") return el;
204
+ el.textContent = "";
205
+ const root = h("div", "ai-threads");
206
+ const items = Array.isArray(model.items) ? model.items : [];
207
+ const active = items.filter((it) => !it.archived);
208
+ const archived = items.filter((it) => it.archived);
209
+ if (model.groups) {
210
+ const ungrouped = active.filter((it) => !(it.group ?? "").trim());
211
+ for (const it of ungrouped) root.appendChild(buildThread(it, model.activeKey));
212
+ const groups = /* @__PURE__ */ new Map();
213
+ for (const it of active) {
214
+ const label = (it.group ?? "").trim();
215
+ if (!label) continue;
216
+ const list = groups.get(label) ?? [];
217
+ list.push(it);
218
+ groups.set(label, list);
219
+ }
220
+ for (const [label, list] of groups) {
221
+ const group = h("div", "ai-threads-group");
222
+ group.dataset.group = label;
223
+ const head = h("button", "ai-threads-group-head", label);
224
+ head.type = "button";
225
+ head.setAttribute("aria-expanded", "true");
226
+ group.appendChild(head);
227
+ const listEl = h("div", "ai-threads-group-list");
228
+ for (const it of list) listEl.appendChild(buildThread(it, model.activeKey));
229
+ group.appendChild(listEl);
230
+ root.appendChild(group);
231
+ }
232
+ if (archived.length > 0) {
233
+ const group = h("div", "ai-threads-group");
234
+ group.dataset.group = ARCHIVED_GROUP;
235
+ const head = h("button", "ai-threads-group-head", ARCHIVED_GROUP);
236
+ head.type = "button";
237
+ head.setAttribute("aria-expanded", "true");
238
+ group.appendChild(head);
239
+ const listEl = h("div", "ai-threads-group-list");
240
+ for (const it of archived) listEl.appendChild(buildThread(it, model.activeKey));
241
+ group.appendChild(listEl);
242
+ root.appendChild(group);
243
+ }
244
+ } else {
245
+ for (const it of active) root.appendChild(buildThread(it, model.activeKey));
246
+ for (const it of archived) root.appendChild(buildThread(it, model.activeKey));
247
+ }
248
+ el.appendChild(root);
249
+ return el;
250
+ }
251
+ function setAiThreadsActive(el, key) {
252
+ const threads = Array.from(el.querySelectorAll(".ai-thread"));
253
+ if (el instanceof HTMLElement && el.classList.contains("ai-thread")) threads.unshift(el);
254
+ for (const t of threads) {
255
+ const hit = key != null && key !== "" && t.dataset.key === key;
256
+ t.classList.toggle("is-active", hit);
257
+ if (hit) t.setAttribute("aria-current", "true");
258
+ else t.removeAttribute("aria-current");
259
+ }
260
+ return el;
261
+ }
262
+ function initAiThreads(root) {
263
+ if (typeof document === "undefined") return () => void 0;
264
+ const scope = root ?? document;
265
+ const marked = scope;
266
+ if (marked.__icenAiThreadsInit) return () => void 0;
267
+ marked.__icenAiThreadsInit = true;
268
+ const target = scope;
269
+ const selectThread = (thread) => {
270
+ const key = thread.dataset.key ?? "";
271
+ dispatch(thread, "icen:ai-thread-select", { key });
272
+ };
273
+ const toggleGroup = (head) => {
274
+ const group = head.closest(".ai-threads-group");
275
+ if (!group) return;
276
+ const open = head.getAttribute("aria-expanded") !== "true";
277
+ head.setAttribute("aria-expanded", String(open));
278
+ group.classList.toggle("is-collapsed", !open);
279
+ };
280
+ const onClick = (e) => {
281
+ const t = e.target instanceof Element ? e.target : null;
282
+ if (!t) return;
283
+ const action = t.closest(".ai-thread-action");
284
+ if (action) {
285
+ const thread2 = action.closest(".ai-thread");
286
+ if (!thread2) return;
287
+ dispatch(thread2, "icen:ai-thread-action", { action: "rename", key: thread2.dataset.key ?? "" });
288
+ return;
289
+ }
290
+ const head = t.closest(".ai-threads-group-head");
291
+ if (head) {
292
+ toggleGroup(head);
293
+ return;
294
+ }
295
+ const thread = t.closest(".ai-thread");
296
+ if (thread) selectThread(thread);
297
+ };
298
+ const onKeydown = (e) => {
299
+ const ke = e;
300
+ if (ke.key !== "Enter" && ke.key !== " ") return;
301
+ const t = ke.target instanceof Element ? ke.target : null;
302
+ const thread = t?.closest(".ai-thread");
303
+ if (!thread || thread.getAttribute("role") !== "button") return;
304
+ ke.preventDefault();
305
+ selectThread(thread);
306
+ };
307
+ target.addEventListener("click", onClick);
308
+ target.addEventListener("keydown", onKeydown);
309
+ return () => {
310
+ target.removeEventListener("click", onClick);
311
+ target.removeEventListener("keydown", onKeydown);
312
+ marked.__icenAiThreadsInit = false;
313
+ };
314
+ }
315
+ export {
316
+ initAiThreads,
317
+ renderAiThreads,
318
+ setAiThreadsActive
319
+ };
@@ -1829,6 +1829,11 @@ function renderChart(el, raw) {
1829
1829
  }
1830
1830
  };
1831
1831
  }
1832
+ var GRAPH_W = 360;
1833
+ var GRAPH_H = 260;
1834
+ var GRAPH_PAD = 28;
1835
+ var GRAPH_LW = GRAPH_W - GRAPH_PAD * 2;
1836
+ var GRAPH_LH = GRAPH_H - GRAPH_PAD * 2;
1832
1837
 
1833
1838
  // src/behaviors/ai-tool.ts
1834
1839
  function setStatusClass(item, status) {
@@ -1448,6 +1448,11 @@ function renderChart(el, raw) {
1448
1448
  }
1449
1449
  };
1450
1450
  }
1451
+ var GRAPH_W = 360;
1452
+ var GRAPH_H = 260;
1453
+ var GRAPH_PAD = 28;
1454
+ var GRAPH_LW = GRAPH_W - GRAPH_PAD * 2;
1455
+ var GRAPH_LH = GRAPH_H - GRAPH_PAD * 2;
1451
1456
 
1452
1457
  // src/behaviors/ai-tools.ts
1453
1458
  var toolRegistry = /* @__PURE__ */ new Map();
@@ -292,5 +292,107 @@ declare function bindChartEvents(root: HTMLElement): () => void;
292
292
  * spec.title 有值时渲染 .chart-title。返回 { el, spec, update, on, destroy }。
293
293
  */
294
294
  declare function renderChart(el: HTMLElement, raw: ChartSpec | unknown): ChartHandle;
295
+ /** 图谱节点(GraphRAG 实体)。weight 缺省以度中心性(邻边数)参与定尺寸 */
296
+ interface GraphNodeSpec {
297
+ id: string;
298
+ label?: string;
299
+ /** 簇 key(社区/Leiden id 等):配色按簇索引走既有调色盘 */
300
+ cluster?: string;
301
+ /** 节点权重(映射半径 6–18px);缺省用度数 */
302
+ weight?: number;
303
+ /** 原始元数据:事件 datum 原样回传(实体卡数据源) */
304
+ meta?: Record<string, unknown>;
305
+ }
306
+ /** 图谱边(默认无向;directed 时带箭头)。weight 映射线宽 0.6–2.5px */
307
+ interface GraphEdgeSpec {
308
+ source: string;
309
+ target: string;
310
+ weight?: number;
311
+ }
312
+ interface GraphSpec extends ChartSpec {
313
+ type: 'graph';
314
+ nodes: GraphNodeSpec[];
315
+ edges: GraphEdgeSpec[];
316
+ /** 有向图:边带箭头(默认无向) */
317
+ directed?: boolean;
318
+ /** 簇 key → 显示名(图例 / tooltip) */
319
+ clusterLabels?: Record<string, string>;
320
+ }
321
+ /** 嵌入地图点:x/y 为预投影 2D 坐标(UMAP/t-SNE 产物;本组件只做视口缩放,不算投影) */
322
+ interface MapPointSpec {
323
+ id: string;
324
+ x: number;
325
+ y: number;
326
+ cluster?: string;
327
+ label?: string;
328
+ meta?: Record<string, unknown>;
329
+ }
330
+ interface MapSpec extends ChartSpec {
331
+ type: 'map';
332
+ points: MapPointSpec[];
333
+ /** 簇 key → 显示名(图例) */
334
+ clusters?: Record<string, string>;
335
+ }
336
+ /** icen:chart-* 的 datum 负载:graph 命中节点 / map 命中点(meta 原样回传) */
337
+ interface ChartDatum {
338
+ id: string;
339
+ label: string;
340
+ cluster?: string;
341
+ meta?: Record<string, unknown>;
342
+ }
343
+ /** graph / map 的事件 detail:既有 ChartEventDetail + datum */
344
+ interface ChartDatumDetail extends ChartEventDetail {
345
+ datum?: ChartDatum;
346
+ }
347
+ /** graph / map 句柄(对齐 ChartHandle 形状;事件 detail 换 ChartDatumDetail) */
348
+ interface DatumChartHandle<S extends GraphSpec | MapSpec = GraphSpec | MapSpec> {
349
+ el: HTMLElement;
350
+ /** 归一后的当前规格 */
351
+ spec: S;
352
+ /** 原地重渲染(graph 重新布局;保留事件委托与句柄) */
353
+ update(next: S | unknown): void;
354
+ /** icen:chart-<name> 监听糖(hover/click/dblclick/contextmenu);返回解绑函数 */
355
+ on(name: ChartEventName, listener: (detail: ChartDatumDetail, event: Event) => void): () => void;
356
+ /** 清空渲染并解绑委托(destroy 后可重新渲染) */
357
+ destroy(): void;
358
+ }
359
+ interface GraphLayoutOptions {
360
+ /** 布局域宽(默认 320) */
361
+ width?: number;
362
+ /** 布局域高(默认 200) */
363
+ height?: number;
364
+ /** 松弛轮数(默认 300) */
365
+ iterations?: number;
366
+ /** 初始环相位偏移(度,默认 0)。无随机源——同输入 + 同 seed 必同布局 */
367
+ seed?: number;
368
+ }
369
+ /** 确定性力导向布局(纯函数,导出便于测试):环初始化 → 松弛 → fit → id → {x, y} */
370
+ declare function layoutGraph(nodes: Array<{
371
+ id: unknown;
372
+ }>, edges: Array<{
373
+ source: unknown;
374
+ target: unknown;
375
+ }>, opts?: GraphLayoutOptions): Map<string, {
376
+ x: number;
377
+ y: number;
378
+ }>;
379
+ /**
380
+ * 力导向知识图谱(GraphRAG 实体关系)。确定性布局(同数据同布局,无随机源);
381
+ * 节点半径 6–18px(weight 归一,缺省用度中心性)、边宽 0.6–2.5px(line-soft)、
382
+ * 簇着色走既有调色盘(无 cluster 单色 tone);节点拖拽 = pointer + 局部松弛
383
+ * 20 轮(rAF 批处理);hover 显示节点 label(≤8 字截断)+ 内置 tooltip;
384
+ * 事件走既有通道 icen:chart-hover/click/dblclick/contextmenu,detail.datum =
385
+ * {id, label, cluster?, meta?}(点击节点回实体卡数据)。图例可点击隐藏簇
386
+ * (icen:chart-legend-toggle;边随任一端簇隐藏淡化)。
387
+ */
388
+ declare function renderGraph(el: HTMLElement, raw: GraphSpec | unknown): DatumChartHandle<GraphSpec>;
389
+ /**
390
+ * embedding 2D 散点地图:坐标为预投影值(UMAP/t-SNE 产物,本函数只做视口
391
+ * min-max 缩放,不算投影);无轴刻度只留细虚线网格;点 4px 半透明
392
+ * (fill-opacity .7,高密度层叠可辨);簇着色走既有调色盘 + 可点击图例。
393
+ * 点击点 → icen:chart-click(detail.datum = {id, label, cluster?, meta?},
394
+ * 点选回 chunk)。TODO: lasso 框选(icen:chart-select + 命中列表),暂不做。
395
+ */
396
+ declare function renderMap(el: HTMLElement, raw: MapSpec | unknown): DatumChartHandle<MapSpec>;
295
397
 
296
- export { type ChartCalendarOptions, type ChartChromeOptions, type ChartDims, type ChartEventDetail, type ChartEventName, type ChartGaugeOptions, type ChartHandle, type ChartHeatmapOptions, type ChartI18nLabels, type ChartLineOptions, type ChartMarkData, type ChartPointSpec, type ChartRadarOptions, type ChartScatterOptions, type ChartSegment, type ChartSegmentsOptions, type ChartSeriesOptions, type ChartSeriesSpec, type ChartSpec, type ChartTone, type ChartType, type ChartVBarOptions, type ChartValueFormat, type HeatmapDatum, type RadarSeries, bindChartEvents, chartFormatValue, inferChartType, normalizeChartSpec, registerChartTone, renderArea, renderCalendar, renderChart, renderDonut, renderGauge, renderHBar, renderHeatmap, renderLine, renderRadar, renderScatter, renderSparkline, renderStack, renderVBar };
398
+ export { type ChartCalendarOptions, type ChartChromeOptions, type ChartDatum, type ChartDatumDetail, type ChartDims, type ChartEventDetail, type ChartEventName, type ChartGaugeOptions, type ChartHandle, type ChartHeatmapOptions, type ChartI18nLabels, type ChartLineOptions, type ChartMarkData, type ChartPointSpec, type ChartRadarOptions, type ChartScatterOptions, type ChartSegment, type ChartSegmentsOptions, type ChartSeriesOptions, type ChartSeriesSpec, type ChartSpec, type ChartTone, type ChartType, type ChartVBarOptions, type ChartValueFormat, type DatumChartHandle, type GraphEdgeSpec, type GraphLayoutOptions, type GraphNodeSpec, type GraphSpec, type HeatmapDatum, type MapPointSpec, type MapSpec, type RadarSeries, bindChartEvents, chartFormatValue, inferChartType, layoutGraph, normalizeChartSpec, registerChartTone, renderArea, renderCalendar, renderChart, renderDonut, renderGauge, renderGraph, renderHBar, renderHeatmap, renderLine, renderMap, renderRadar, renderScatter, renderSparkline, renderStack, renderVBar };