@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
@@ -0,0 +1,948 @@
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/kb-core.ts
167
+ var ic = (label, d) => ({ label, icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${d}</svg>` });
168
+ var BUILTIN_SOURCE_TYPES = {
169
+ doc: ic("\u6587\u6863", '<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"/>'),
170
+ web: ic("\u7F51\u9875", '<circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20z"/>'),
171
+ wiki: ic("\u77E5\u8BC6\u5E93", '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>'),
172
+ sheet: ic("\u8868\u683C", '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/>'),
173
+ db: ic("\u6570\u636E\u5E93", '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/>'),
174
+ ticket: ic("\u5DE5\u5355", '<path d="M3 9V7a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2a2 2 0 0 0 0 6v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2a2 2 0 0 0 0-6z"/><path d="M13 5v14"/>'),
175
+ code: ic("\u4EE3\u7801", '<path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/>'),
176
+ api: ic("\u63A5\u53E3", '<path d="M4 7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v3a2 2 0 0 0 0 4v3a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-3a2 2 0 0 0 0-4z"/>'),
177
+ image: ic("\u56FE\u7247", '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-4.6-4.6a2 2 0 0 0-2.8 0L3 21"/>'),
178
+ mail: ic("\u90AE\u4EF6", '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 7L2 7"/>')
179
+ };
180
+ var sourceTypeRegistry = new Map(Object.entries(BUILTIN_SOURCE_TYPES));
181
+ function normalizeSqlQuery(raw) {
182
+ return {
183
+ sql: String(raw.sql ?? ""),
184
+ dialect: typeof raw.dialect === "string" ? raw.dialect : void 0,
185
+ tables: Array.isArray(raw.tables) ? raw.tables.map(String) : void 0,
186
+ visibility: raw.visibility === "admin" ? "admin" : "all",
187
+ editable: raw.editable !== false,
188
+ prevSql: typeof raw.prevSql === "string" ? raw.prevSql : void 0,
189
+ reasoning: typeof raw.reasoning === "string" ? raw.reasoning : void 0
190
+ };
191
+ }
192
+
193
+ // src/behaviors/events.ts
194
+ function emitIcen(source, type, detail) {
195
+ return source.dispatchEvent(
196
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
197
+ );
198
+ }
199
+
200
+ // src/behaviors/ai-diff.ts
201
+ var RE_HUNK = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
202
+ var RE_OLD_FILE = /^--- (.*)$/;
203
+ var RE_NEW_FILE_LINE = /^\+\+\+ (.*)$/;
204
+ var RE_NEW_MODE = /^new file mode /;
205
+ var RE_DEL_MODE = /^deleted file mode /;
206
+ var RE_RENAME_FROM = /^rename from (.*)$/;
207
+ var RE_RENAME_TO = /^rename to (.*)$/;
208
+ var RE_BINARY = /^Binary files .* differ$/;
209
+ function parseDiffPath(token) {
210
+ let p = token.split(" ")[0].trim();
211
+ if (p.length >= 2 && p.startsWith('"') && p.endsWith('"')) p = p.slice(1, -1);
212
+ if (p !== "/dev/null" && (p.startsWith("a/") || p.startsWith("b/"))) p = p.slice(2);
213
+ return p;
214
+ }
215
+ function parseGitHeader(line) {
216
+ const m = /^diff --git\s+(\S+)\s+(.+)$/.exec(line);
217
+ if (!m) return null;
218
+ return { oldPath: parseDiffPath(m[1]), newPath: parseDiffPath(m[2]) };
219
+ }
220
+ function emptyFile(path) {
221
+ return { path, status: "modified", hunks: [], addCount: 0, delCount: 0 };
222
+ }
223
+ function parseUnifiedDiff(text) {
224
+ const files = [];
225
+ if (typeof text !== "string" || text.length === 0) return files;
226
+ let cur = null;
227
+ let hunk = null;
228
+ let oldNo = 0;
229
+ let newNo = 0;
230
+ const lines = text.split("\n");
231
+ for (const raw of lines) {
232
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
233
+ const git = parseGitHeader(line);
234
+ if (git) {
235
+ cur = emptyFile(git.newPath);
236
+ cur.oldPath = git.oldPath;
237
+ files.push(cur);
238
+ hunk = null;
239
+ continue;
240
+ }
241
+ if (!cur && RE_OLD_FILE.test(line)) {
242
+ cur = emptyFile("");
243
+ files.push(cur);
244
+ hunk = null;
245
+ }
246
+ if (!cur) continue;
247
+ if (RE_NEW_MODE.test(line)) {
248
+ cur.status = "added";
249
+ continue;
250
+ }
251
+ if (RE_DEL_MODE.test(line)) {
252
+ cur.status = "deleted";
253
+ continue;
254
+ }
255
+ const renameFrom = RE_RENAME_FROM.exec(line);
256
+ if (renameFrom) {
257
+ cur.oldPath = parseDiffPath(renameFrom[1]);
258
+ cur.status = "renamed";
259
+ continue;
260
+ }
261
+ const renameTo = RE_RENAME_TO.exec(line);
262
+ if (renameTo) {
263
+ cur.path = parseDiffPath(renameTo[1]);
264
+ cur.status = "renamed";
265
+ continue;
266
+ }
267
+ const oldLine = RE_OLD_FILE.exec(line);
268
+ if (oldLine) {
269
+ const p = parseDiffPath(oldLine[1]);
270
+ if (p === "/dev/null") cur.status = "added";
271
+ else if (!cur.oldPath) cur.oldPath = p;
272
+ continue;
273
+ }
274
+ const newLine = RE_NEW_FILE_LINE.exec(line);
275
+ if (newLine) {
276
+ const p = parseDiffPath(newLine[1]);
277
+ if (p === "/dev/null") cur.status = "deleted";
278
+ else if (p) cur.path = p;
279
+ continue;
280
+ }
281
+ if (RE_BINARY.test(line)) {
282
+ cur.binary = true;
283
+ hunk = null;
284
+ continue;
285
+ }
286
+ const hm = RE_HUNK.exec(line);
287
+ if (hm) {
288
+ hunk = {
289
+ oldStart: Number(hm[1]),
290
+ oldCount: hm[2] ? Number(hm[2]) : 1,
291
+ newStart: Number(hm[3]),
292
+ newCount: hm[4] ? Number(hm[4]) : 1,
293
+ header: line,
294
+ lines: []
295
+ };
296
+ cur.hunks.push(hunk);
297
+ oldNo = hunk.oldStart;
298
+ newNo = hunk.newStart;
299
+ continue;
300
+ }
301
+ if (!hunk) continue;
302
+ if (line.startsWith("\\")) continue;
303
+ if (line.startsWith("+")) {
304
+ hunk.lines.push({ type: "add", oldNo: null, newNo: newNo++, text: line.slice(1) });
305
+ cur.addCount++;
306
+ } else if (line.startsWith("-")) {
307
+ hunk.lines.push({ type: "del", oldNo: oldNo++, newNo: null, text: line.slice(1) });
308
+ cur.delCount++;
309
+ } else {
310
+ const body = line.startsWith(" ") ? line.slice(1) : line;
311
+ hunk.lines.push({ type: "ctx", oldNo: oldNo++, newNo: newNo++, text: body });
312
+ }
313
+ }
314
+ return files;
315
+ }
316
+ function renderLineRow(line) {
317
+ const tr = document.createElement("tr");
318
+ tr.className = `ai-diff-line--${line.type}`;
319
+ if (line.type === "hunk") {
320
+ const td = h("td", "", line.text);
321
+ td.colSpan = 3;
322
+ tr.appendChild(td);
323
+ return tr;
324
+ }
325
+ const oldTd = h("td", "ai-diff-ln", line.oldNo === null ? "" : String(line.oldNo));
326
+ oldTd.setAttribute("aria-hidden", "true");
327
+ const newTd = h("td", "ai-diff-ln", line.newNo === null ? "" : String(line.newNo));
328
+ newTd.setAttribute("aria-hidden", "true");
329
+ tr.appendChild(oldTd);
330
+ tr.appendChild(newTd);
331
+ tr.appendChild(h("td", "ai-diff-code", line.text));
332
+ return tr;
333
+ }
334
+ function renderFile(file) {
335
+ const root = h("div", `ai-diff-file is-${file.status}`);
336
+ root.dataset.path = file.path;
337
+ const head = h("div", "ai-diff-head");
338
+ head.setAttribute("role", "button");
339
+ head.tabIndex = 0;
340
+ head.setAttribute("aria-expanded", "false");
341
+ const pathText = file.status === "renamed" && file.oldPath ? `${file.oldPath} \u2192 ${file.path}` : file.path;
342
+ head.appendChild(h("span", "ai-diff-path", pathText));
343
+ const stat = h("span", "ai-diff-stat");
344
+ if (file.addCount > 0) stat.appendChild(h("b", "ai-diff-add", `+${file.addCount}`));
345
+ if (file.delCount > 0) stat.appendChild(h("b", "ai-diff-del", `\u2212${file.delCount}`));
346
+ if (stat.children.length > 0) head.appendChild(stat);
347
+ const actions = h("span", "ai-diff-actions");
348
+ actions.appendChild(h("button", "", "\u63A5\u53D7")).setAttribute("data-ai-diff-accept", "");
349
+ const rejectBtn = h("button", "", "\u62D2\u7EDD");
350
+ rejectBtn.setAttribute("data-ai-diff-reject", "");
351
+ actions.appendChild(rejectBtn);
352
+ head.appendChild(actions);
353
+ const verdict = h("span", "ai-diff-verdict");
354
+ verdict.hidden = true;
355
+ head.appendChild(verdict);
356
+ root.appendChild(head);
357
+ const body = h("div", "ai-diff-body");
358
+ body.hidden = true;
359
+ const table = h("table", "ai-diff-table");
360
+ if (file.hunks.length === 0) {
361
+ const tr = document.createElement("tr");
362
+ tr.className = "ai-diff-line--ctx";
363
+ 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");
364
+ td.colSpan = 3;
365
+ tr.appendChild(td);
366
+ table.appendChild(tr);
367
+ } else {
368
+ for (const hunk of file.hunks) {
369
+ table.appendChild(renderLineRow({ type: "hunk", oldNo: null, newNo: null, text: hunk.header }));
370
+ for (const line of hunk.lines) table.appendChild(renderLineRow(line));
371
+ }
372
+ }
373
+ body.appendChild(table);
374
+ root.appendChild(body);
375
+ return root;
376
+ }
377
+ function renderAiDiff(el, model) {
378
+ if (typeof document === "undefined") return el;
379
+ el.textContent = "";
380
+ const root = h("div", "ai-diff");
381
+ const files = Array.isArray(model?.files) ? model.files : [];
382
+ for (const file of files) root.appendChild(renderFile(file));
383
+ el.appendChild(root);
384
+ return el;
385
+ }
386
+ var noopDestroy = () => void 0;
387
+ function closestFile(node) {
388
+ const file = node?.closest(".ai-diff-file");
389
+ return file instanceof HTMLElement ? file : null;
390
+ }
391
+ function filePath(file) {
392
+ if (file.dataset.path) return file.dataset.path;
393
+ return file.querySelector(".ai-diff-path")?.textContent ?? "";
394
+ }
395
+ function toggleFile(head) {
396
+ const file = closestFile(head);
397
+ if (!file) return;
398
+ const body = file.querySelector(".ai-diff-body");
399
+ if (!body) return;
400
+ const open = body.hidden;
401
+ body.hidden = !open;
402
+ head.setAttribute("aria-expanded", String(open));
403
+ emitIcen(file, "icen:ai-toggle", { el: file, open });
404
+ }
405
+ function decide(btn, accepted) {
406
+ const file = closestFile(btn);
407
+ if (!file) return;
408
+ if (file.classList.contains("is-accepted") || file.classList.contains("is-rejected")) return;
409
+ file.classList.add(accepted ? "is-accepted" : "is-rejected");
410
+ let verdict = file.querySelector(".ai-diff-verdict");
411
+ if (!verdict) {
412
+ verdict = h("span", "ai-diff-verdict");
413
+ file.querySelector(".ai-diff-head")?.appendChild(verdict);
414
+ }
415
+ verdict.textContent = accepted ? "\u5DF2\u63A5\u53D7" : "\u5DF2\u62D2\u7EDD";
416
+ verdict.hidden = false;
417
+ file.querySelectorAll(
418
+ "[data-ai-diff-accept], [data-ai-diff-reject]"
419
+ ).forEach((b) => {
420
+ b.hidden = true;
421
+ b.disabled = true;
422
+ });
423
+ emitIcen(file, accepted ? "icen:ai-diff-accept" : "icen:ai-diff-reject", { path: filePath(file) });
424
+ }
425
+ function initAiDiff(root) {
426
+ if (typeof document === "undefined") return noopDestroy;
427
+ const scope = root ?? document;
428
+ const marked = scope;
429
+ if (marked.__icenAiDiffInit) return noopDestroy;
430
+ marked.__icenAiDiffInit = true;
431
+ const target = scope;
432
+ const onClick = (e) => {
433
+ const t = e.target instanceof Element ? e.target : null;
434
+ if (!t) return;
435
+ const acceptBtn = t.closest("[data-ai-diff-accept]");
436
+ if (acceptBtn) {
437
+ e.stopPropagation();
438
+ decide(acceptBtn, true);
439
+ return;
440
+ }
441
+ const rejectBtn = t.closest("[data-ai-diff-reject]");
442
+ if (rejectBtn) {
443
+ e.stopPropagation();
444
+ decide(rejectBtn, false);
445
+ return;
446
+ }
447
+ const head = t.closest(".ai-diff-head");
448
+ if (head) toggleFile(head);
449
+ };
450
+ const onKeydown = (e) => {
451
+ const ke = e;
452
+ if (ke.key !== "Enter" && ke.key !== " ") return;
453
+ const t = ke.target instanceof Element ? ke.target : null;
454
+ if (!t || t.closest("button, a, input, textarea, select")) return;
455
+ const head = t.closest(".ai-diff-head");
456
+ if (!head) return;
457
+ ke.preventDefault();
458
+ toggleFile(head);
459
+ };
460
+ target.addEventListener("click", onClick);
461
+ target.addEventListener("keydown", onKeydown);
462
+ return () => {
463
+ target.removeEventListener("click", onClick);
464
+ target.removeEventListener("keydown", onKeydown);
465
+ marked.__icenAiDiffInit = false;
466
+ };
467
+ }
468
+
469
+ // src/behaviors/kb-sql.ts
470
+ var ico = (d) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${d}</svg>`;
471
+ var ICON_COPY = ico('<rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>');
472
+ var ICON_EDIT = ico('<path d="M17 3a2.8 2.8 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/>');
473
+ var ICON_WRENCH = ico('<path d="M14.7 6.3a4.5 4.5 0 0 0-6 5.6L3 17.6V21h3.4l5.7-5.7a4.5 4.5 0 0 0 5.6-6L14.5 12l-2.5-2.5Z"/>');
474
+ var SQL_KEYWORDS = /* @__PURE__ */ new Set([
475
+ "SELECT",
476
+ "FROM",
477
+ "WHERE",
478
+ "GROUP",
479
+ "BY",
480
+ "ORDER",
481
+ "JOIN",
482
+ "ON",
483
+ "AND",
484
+ "OR",
485
+ "NOT",
486
+ "LIMIT",
487
+ "HAVING",
488
+ "WITH",
489
+ "AS",
490
+ "UNION",
491
+ "ALL",
492
+ "INSERT",
493
+ "INTO",
494
+ "VALUES",
495
+ "UPDATE",
496
+ "SET",
497
+ "DELETE",
498
+ "CREATE",
499
+ "TABLE",
500
+ "VIEW",
501
+ "INDEX",
502
+ "DROP",
503
+ "ALTER",
504
+ "ADD",
505
+ "DISTINCT",
506
+ "CASE",
507
+ "WHEN",
508
+ "THEN",
509
+ "ELSE",
510
+ "END",
511
+ "INNER",
512
+ "LEFT",
513
+ "RIGHT",
514
+ "FULL",
515
+ "OUTER",
516
+ "CROSS",
517
+ "NATURAL",
518
+ "USING",
519
+ "ASC",
520
+ "DESC",
521
+ "NULLS",
522
+ "IS",
523
+ "NULL",
524
+ "IN",
525
+ "LIKE",
526
+ "ILIKE",
527
+ "BETWEEN",
528
+ "EXISTS",
529
+ "OVER",
530
+ "PARTITION",
531
+ "WINDOW",
532
+ "OFFSET",
533
+ "FETCH",
534
+ "EXCEPT",
535
+ "INTERSECT",
536
+ "PRIMARY",
537
+ "FOREIGN",
538
+ "KEY",
539
+ "REFERENCES",
540
+ "DEFAULT",
541
+ "UNIQUE",
542
+ "CONSTRAINT",
543
+ "CHECK",
544
+ "TRUE",
545
+ "FALSE",
546
+ "INTERVAL",
547
+ "RECURSIVE",
548
+ "RETURNING",
549
+ "CONFLICT"
550
+ ]);
551
+ var isDigit = (c) => c >= "0" && c <= "9";
552
+ var isWordHead = (c) => c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
553
+ var isWordChar = (c) => isWordHead(c) || isDigit(c);
554
+ var isPlain = (c) => !isDigit(c) && !isWordHead(c) && c !== "'" && c !== "-" && c !== "/";
555
+ function tokenizeSql(sql) {
556
+ const out = [];
557
+ const n = sql.length;
558
+ let i = 0;
559
+ while (i < n) {
560
+ const ch = sql[i];
561
+ if (ch === "-" && sql[i + 1] === "-") {
562
+ const nl = sql.indexOf("\n", i);
563
+ const end = nl === -1 ? n : nl;
564
+ out.push({ text: sql.slice(i, end), kind: "cmt" });
565
+ i = end;
566
+ continue;
567
+ }
568
+ if (ch === "/" && sql[i + 1] === "*") {
569
+ const close = sql.indexOf("*/", i + 2);
570
+ if (close === -1) {
571
+ out.push({ text: sql.slice(i), kind: null });
572
+ break;
573
+ }
574
+ out.push({ text: sql.slice(i, close + 2), kind: "cmt" });
575
+ i = close + 2;
576
+ continue;
577
+ }
578
+ if (ch === "'") {
579
+ let j2 = i + 1;
580
+ let closed = false;
581
+ while (j2 < n) {
582
+ if (sql[j2] === "'") {
583
+ if (sql[j2 + 1] === "'") {
584
+ j2 += 2;
585
+ continue;
586
+ }
587
+ closed = true;
588
+ break;
589
+ }
590
+ j2++;
591
+ }
592
+ if (!closed) {
593
+ out.push({ text: sql.slice(i), kind: null });
594
+ break;
595
+ }
596
+ out.push({ text: sql.slice(i, j2 + 1), kind: "str" });
597
+ i = j2 + 1;
598
+ continue;
599
+ }
600
+ if (isDigit(ch)) {
601
+ let j2 = i;
602
+ while (j2 < n && (isDigit(sql[j2]) || sql[j2] === ".")) j2++;
603
+ out.push({ text: sql.slice(i, j2), kind: "num" });
604
+ i = j2;
605
+ continue;
606
+ }
607
+ if (isWordHead(ch)) {
608
+ let j2 = i;
609
+ while (j2 < n && isWordChar(sql[j2])) j2++;
610
+ const word = sql.slice(i, j2);
611
+ let kind = null;
612
+ if (SQL_KEYWORDS.has(word.toUpperCase())) {
613
+ kind = "kw";
614
+ } else {
615
+ let k = j2;
616
+ while (k < n && (sql[k] === " " || sql[k] === " ")) k++;
617
+ if (sql[k] === "(") kind = "fn";
618
+ }
619
+ out.push({ text: word, kind });
620
+ i = j2;
621
+ continue;
622
+ }
623
+ let j = i;
624
+ while (j < n && isPlain(sql[j])) j++;
625
+ if (j === i) j = i + 1;
626
+ out.push({ text: sql.slice(i, j), kind: null });
627
+ i = j;
628
+ }
629
+ return out;
630
+ }
631
+ function highlightSql(sql) {
632
+ const frag = document.createDocumentFragment();
633
+ for (const t of tokenizeSql(String(sql ?? ""))) {
634
+ if (t.kind) {
635
+ const span = document.createElement("span");
636
+ span.className = `kb-sql-tok--${t.kind}`;
637
+ span.textContent = t.text;
638
+ frag.appendChild(span);
639
+ } else {
640
+ frag.appendChild(document.createTextNode(t.text));
641
+ }
642
+ }
643
+ return frag;
644
+ }
645
+ function diffLines(a, b) {
646
+ const n = a.length;
647
+ const m = b.length;
648
+ if (n * m > 1e6) {
649
+ return [
650
+ ...a.map((text) => ({ t: "del", text })),
651
+ ...b.map((text) => ({ t: "add", text }))
652
+ ];
653
+ }
654
+ const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
655
+ for (let i2 = n - 1; i2 >= 0; i2--) {
656
+ for (let j2 = m - 1; j2 >= 0; j2--) {
657
+ dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
658
+ }
659
+ }
660
+ const ops = [];
661
+ let i = 0;
662
+ let j = 0;
663
+ while (i < n && j < m) {
664
+ if (a[i] === b[j]) {
665
+ ops.push({ t: "ctx", text: a[i] });
666
+ i++;
667
+ j++;
668
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
669
+ ops.push({ t: "del", text: a[i] });
670
+ i++;
671
+ } else {
672
+ ops.push({ t: "add", text: b[j] });
673
+ j++;
674
+ }
675
+ }
676
+ while (i < n) ops.push({ t: "del", text: a[i++] });
677
+ while (j < m) ops.push({ t: "add", text: b[j++] });
678
+ return ops;
679
+ }
680
+ function makeSqlDiff(a, b) {
681
+ const ops = diffLines(String(a ?? "").split("\n"), String(b ?? "").split("\n"));
682
+ const CTX = 3;
683
+ const n = ops.length;
684
+ const oldAt = new Array(n + 1);
685
+ const newAt = new Array(n + 1);
686
+ oldAt[0] = 1;
687
+ newAt[0] = 1;
688
+ for (let k = 0; k < n; k++) {
689
+ oldAt[k + 1] = oldAt[k] + (ops[k].t === "add" ? 0 : 1);
690
+ newAt[k + 1] = newAt[k] + (ops[k].t === "del" ? 0 : 1);
691
+ }
692
+ const hunks = [];
693
+ let i = 0;
694
+ while (i < n) {
695
+ let change = -1;
696
+ for (let k = i; k < n; k++) {
697
+ if (ops[k].t !== "ctx") {
698
+ change = k;
699
+ break;
700
+ }
701
+ }
702
+ if (change === -1) break;
703
+ const start = Math.max(0, change - CTX);
704
+ let end = change;
705
+ let sinceChange = 0;
706
+ for (let k = change; k < n; k++) {
707
+ if (ops[k].t !== "ctx") {
708
+ sinceChange = 0;
709
+ end = k;
710
+ } else {
711
+ sinceChange++;
712
+ if (sinceChange <= CTX) end = k;
713
+ else break;
714
+ }
715
+ }
716
+ const stop = end + 1;
717
+ const slice = ops.slice(start, stop);
718
+ const oldCount = slice.filter((o) => o.t !== "add").length;
719
+ const newCount = slice.filter((o) => o.t !== "del").length;
720
+ const oStart = oldCount === 0 ? Math.max(0, oldAt[start] - 1) : oldAt[start];
721
+ const nStart = newCount === 0 ? Math.max(0, newAt[start] - 1) : newAt[start];
722
+ const body = slice.map((o) => `${o.t === "del" ? "-" : o.t === "add" ? "+" : " "}${o.text}`).join("\n");
723
+ hunks.push(`@@ -${oStart},${oldCount} +${nStart},${newCount} @@
724
+ ${body}`);
725
+ i = stop;
726
+ }
727
+ return ["diff --git a/prev.sql b/sql.sql", "--- a/prev.sql", "+++ b/sql.sql", ...hunks].join("\n");
728
+ }
729
+ function iconBtn(cls, dataAttr, icon, label) {
730
+ const btn = h("button", cls);
731
+ btn.type = "button";
732
+ btn.setAttribute(dataAttr, "");
733
+ const iconBox = h("span", "kb-sql-btn-icon");
734
+ const svg2 = svgIcon(icon);
735
+ if (svg2) iconBox.appendChild(svg2);
736
+ btn.appendChild(iconBox);
737
+ btn.appendChild(h("span", "kb-sql-btn-text", label));
738
+ return btn;
739
+ }
740
+ function renderKbSql(el, query, opts) {
741
+ if (typeof document === "undefined") return el;
742
+ const q = normalizeSqlQuery(query);
743
+ el.textContent = "";
744
+ const card = h("div", "kb-sql");
745
+ card.dataset.kbSql = "";
746
+ const head = h("div", "kb-sql-head");
747
+ head.appendChild(h("span", "kb-sql-title", opts?.title ?? "\u751F\u6210\u7684 SQL"));
748
+ if (q.visibility === "admin") head.appendChild(h("span", "kb-badge kb-badge--admin", "\u4EC5\u7BA1\u7406\u5458\u53EF\u89C1"));
749
+ if (q.dialect) head.appendChild(h("span", "kb-meta", q.dialect));
750
+ if (q.tables?.length) {
751
+ const tables = h("span", "kb-sql-tables");
752
+ for (const t of q.tables) tables.appendChild(h("span", "kb-chip", t));
753
+ head.appendChild(tables);
754
+ }
755
+ const actions = h("span", "kb-sql-actions");
756
+ actions.appendChild(iconBtn("kb-sql-btn", "data-kb-sql-copy", ICON_COPY, "\u590D\u5236"));
757
+ if (q.editable) actions.appendChild(iconBtn("kb-sql-btn", "data-kb-sql-edit", ICON_EDIT, "\u7F16\u8F91\u91CD\u8DD1"));
758
+ head.appendChild(actions);
759
+ card.appendChild(head);
760
+ const pre = h("pre", "kb-sql-code");
761
+ pre.setAttribute("data-kb-sql-code", "");
762
+ pre.appendChild(highlightSql(q.sql));
763
+ card.appendChild(pre);
764
+ if (typeof q.prevSql === "string" && q.prevSql !== q.sql) {
765
+ const fix = h("div", "kb-sql-fix");
766
+ const fixHead = h("div", "kb-sql-fix-head");
767
+ const fixTitle = h("span", "kb-sql-fix-title");
768
+ const warnSvg = svgIcon(ico('<path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>'));
769
+ if (warnSvg) fixTitle.appendChild(warnSvg);
770
+ fixTitle.appendChild(h("span", "", "\u68C0\u6D4B\u5230\u4E0A\u7248\u62A5\u9519\uFF0C\u4FEE\u590D\u5EFA\u8BAE\u5DF2\u5C31\u7EEA"));
771
+ fixHead.appendChild(fixTitle);
772
+ const applyBtn = iconBtn("kb-sql-btn kb-sql-btn--apply", "data-kb-sql-apply", ICON_WRENCH, "\u5E94\u7528\u4FEE\u590D\u5E76\u91CD\u8DD1");
773
+ fixHead.appendChild(applyBtn);
774
+ fix.appendChild(fixHead);
775
+ const diffBox = h("div", "kb-sql-fix-diff");
776
+ renderAiDiff(diffBox, { files: parseUnifiedDiff(makeSqlDiff(q.prevSql, q.sql)) });
777
+ diffBox.querySelectorAll(".ai-diff-file").forEach((file) => {
778
+ const body = file.querySelector(".ai-diff-body");
779
+ if (body) body.hidden = false;
780
+ file.querySelector(".ai-diff-head")?.setAttribute("aria-expanded", "true");
781
+ });
782
+ fix.appendChild(diffBox);
783
+ card.appendChild(fix);
784
+ if (opts?.onRerun) {
785
+ const onRerun = opts.onRerun;
786
+ applyBtn.addEventListener("click", () => onRerun(pre.textContent ?? ""));
787
+ }
788
+ }
789
+ if (q.editable) {
790
+ const editor = h("div", "kb-sql-editor");
791
+ editor.hidden = true;
792
+ const ta = document.createElement("textarea");
793
+ ta.className = "kb-sql-textarea";
794
+ ta.setAttribute("data-kb-sql-textarea", "");
795
+ ta.spellcheck = false;
796
+ editor.appendChild(ta);
797
+ const editorActions = h("div", "kb-sql-editor-actions");
798
+ const cancelBtn = h("button", "kb-sql-btn", "\u53D6\u6D88");
799
+ cancelBtn.type = "button";
800
+ cancelBtn.setAttribute("data-kb-sql-cancel", "");
801
+ const confirmBtn = h("button", "kb-sql-btn kb-sql-btn--run", "\u786E\u8BA4\u5E76\u91CD\u8DD1");
802
+ confirmBtn.type = "button";
803
+ confirmBtn.setAttribute("data-kb-sql-confirm", "");
804
+ editorActions.appendChild(cancelBtn);
805
+ editorActions.appendChild(confirmBtn);
806
+ editor.appendChild(editorActions);
807
+ card.appendChild(editor);
808
+ const onEdit = opts?.onEdit;
809
+ const onRerun = opts?.onRerun;
810
+ if (onEdit || onRerun) {
811
+ confirmBtn.addEventListener("click", () => {
812
+ const sql = ta.value;
813
+ if (!sql.trim()) return;
814
+ onEdit?.(sql);
815
+ onRerun?.(sql);
816
+ });
817
+ }
818
+ }
819
+ el.appendChild(card);
820
+ return el;
821
+ }
822
+ var noopDestroy2 = () => void 0;
823
+ var copyTimers = /* @__PURE__ */ new WeakMap();
824
+ function flashCopied(textSpan) {
825
+ textSpan.textContent = "\u5DF2\u590D\u5236";
826
+ const prev = copyTimers.get(textSpan);
827
+ if (prev != null) clearTimeout(prev);
828
+ copyTimers.set(
829
+ textSpan,
830
+ window.setTimeout(() => {
831
+ textSpan.textContent = "\u590D\u5236";
832
+ copyTimers.delete(textSpan);
833
+ }, 1200)
834
+ );
835
+ }
836
+ function copyText(text) {
837
+ if (navigator.clipboard?.writeText) {
838
+ void navigator.clipboard.writeText(text).catch(() => void 0);
839
+ return;
840
+ }
841
+ const ta = document.createElement("textarea");
842
+ ta.value = text;
843
+ ta.style.position = "fixed";
844
+ ta.style.opacity = "0";
845
+ document.body.appendChild(ta);
846
+ ta.select();
847
+ try {
848
+ document.execCommand("copy");
849
+ } catch {
850
+ }
851
+ ta.remove();
852
+ }
853
+ function setEditMode(card, editing) {
854
+ const pre = card.querySelector(".kb-sql-code");
855
+ const editor = card.querySelector(".kb-sql-editor");
856
+ const ta = card.querySelector(".kb-sql-textarea");
857
+ if (!pre || !editor || !ta) return;
858
+ editor.hidden = !editing;
859
+ pre.hidden = editing;
860
+ card.classList.toggle("is-editing", editing);
861
+ if (editing) {
862
+ ta.value = pre.textContent ?? "";
863
+ ta.focus();
864
+ }
865
+ }
866
+ function confirmEdit(card, source) {
867
+ const ta = card.querySelector(".kb-sql-textarea");
868
+ const sql = ta?.value ?? "";
869
+ if (!sql.trim()) return;
870
+ const pre = card.querySelector(".kb-sql-code");
871
+ if (pre) {
872
+ pre.textContent = "";
873
+ pre.appendChild(highlightSql(sql));
874
+ }
875
+ setEditMode(card, false);
876
+ emitIcen(source, "icen:kb-sql-edit", { sql });
877
+ emitIcen(source, "icen:kb-sql-rerun", { sql });
878
+ }
879
+ function initKbSql(root) {
880
+ if (typeof document === "undefined") return noopDestroy2;
881
+ const scope = root ?? document;
882
+ const marked = scope;
883
+ if (marked.__icenKbSqlInit) return noopDestroy2;
884
+ marked.__icenKbSqlInit = true;
885
+ const destroyAiDiff = initAiDiff(scope);
886
+ const target = scope;
887
+ const currentSql = (card) => card.querySelector(".kb-sql-code")?.textContent ?? "";
888
+ const onClick = (e) => {
889
+ const t = e.target instanceof Element ? e.target : null;
890
+ if (!t) return;
891
+ const card = t.closest("[data-kb-sql]");
892
+ if (!card) return;
893
+ const copyBtn = t.closest("[data-kb-sql-copy]");
894
+ if (copyBtn) {
895
+ copyText(currentSql(card));
896
+ const label = copyBtn.querySelector(".kb-sql-btn-text");
897
+ if (label instanceof HTMLElement) flashCopied(label);
898
+ return;
899
+ }
900
+ if (t.closest("[data-kb-sql-edit]")) {
901
+ setEditMode(card, true);
902
+ return;
903
+ }
904
+ if (t.closest("[data-kb-sql-cancel]")) {
905
+ setEditMode(card, false);
906
+ return;
907
+ }
908
+ const confirmBtn = t.closest("[data-kb-sql-confirm]");
909
+ if (confirmBtn) {
910
+ confirmEdit(card, confirmBtn);
911
+ return;
912
+ }
913
+ const applyBtn = t.closest("[data-kb-sql-apply]");
914
+ if (applyBtn) {
915
+ emitIcen(applyBtn, "icen:kb-sql-rerun", { sql: currentSql(card) });
916
+ }
917
+ };
918
+ const onKeydown = (e) => {
919
+ const ke = e;
920
+ const t = ke.target instanceof Element ? ke.target : null;
921
+ if (!t || !t.matches("[data-kb-sql-textarea]")) return;
922
+ const card = t.closest("[data-kb-sql]");
923
+ if (!card) return;
924
+ if (ke.key === "Escape") {
925
+ ke.preventDefault();
926
+ setEditMode(card, false);
927
+ return;
928
+ }
929
+ if (ke.key === "Enter" && (ke.ctrlKey || ke.metaKey)) {
930
+ ke.preventDefault();
931
+ confirmEdit(card, t);
932
+ }
933
+ };
934
+ target.addEventListener("click", onClick);
935
+ target.addEventListener("keydown", onKeydown);
936
+ return () => {
937
+ target.removeEventListener("click", onClick);
938
+ target.removeEventListener("keydown", onKeydown);
939
+ marked.__icenKbSqlInit = false;
940
+ destroyAiDiff();
941
+ };
942
+ }
943
+ export {
944
+ highlightSql,
945
+ initKbSql,
946
+ makeSqlDiff,
947
+ renderKbSql
948
+ };