@icen.ai/ui 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (362) hide show
  1. package/AGENTS.md +79 -309
  2. package/LICENSE +21 -0
  3. package/README.md +43 -54
  4. package/dist/base.css +68 -22
  5. package/dist/behaviors/accordion.mjs +1 -1
  6. package/dist/behaviors/ai-branch.mjs +14 -0
  7. package/dist/behaviors/ai-chat.d.mts +2 -2
  8. package/dist/behaviors/ai-chat.mjs +66 -10
  9. package/dist/behaviors/ai-composer.d.mts +5 -5
  10. package/dist/behaviors/ai-composer.mjs +34 -10
  11. package/dist/behaviors/ai-core.mjs +14 -0
  12. package/dist/behaviors/ai-diff.mjs +14 -0
  13. package/dist/behaviors/ai-feedback.mjs +14 -0
  14. package/dist/behaviors/ai-panel.d.mts +2 -2
  15. package/dist/behaviors/ai-panel.mjs +14 -0
  16. package/dist/behaviors/ai-provider.d.mts +1 -1
  17. package/dist/behaviors/ai-provider.mjs +14 -0
  18. package/dist/behaviors/ai-threads.mjs +14 -0
  19. package/dist/behaviors/ai-tool.d.mts +1 -1
  20. package/dist/behaviors/ai-tool.mjs +299 -24
  21. package/dist/behaviors/ai-tools.mjs +1179 -28
  22. package/dist/behaviors/autocomplete.d.mts +1 -1
  23. package/dist/behaviors/autocomplete.mjs +14 -0
  24. package/dist/behaviors/back-top.mjs +14 -0
  25. package/dist/behaviors/carousel.mjs +21 -1
  26. package/dist/behaviors/cascader.mjs +27 -0
  27. package/dist/behaviors/charts.d.mts +12 -1
  28. package/dist/behaviors/charts.mjs +298 -20
  29. package/dist/behaviors/code-block.mjs +22 -1
  30. package/dist/behaviors/command-palette.mjs +3 -3
  31. package/dist/behaviors/context-menu.d.mts +2 -2
  32. package/dist/behaviors/context-menu.mjs +14 -0
  33. package/dist/behaviors/controls.d.mts +8 -8
  34. package/dist/behaviors/controls.mjs +50 -6
  35. package/dist/behaviors/copy.mjs +14 -0
  36. package/dist/behaviors/datatable.d.mts +3 -3
  37. package/dist/behaviors/datatable.mjs +89 -11
  38. package/dist/behaviors/date-picker.mjs +14 -0
  39. package/dist/behaviors/events.d.mts +10 -10
  40. package/dist/behaviors/events.mjs +11 -1
  41. package/dist/behaviors/form-validate.d.mts +10 -1
  42. package/dist/behaviors/form-validate.mjs +279 -4611
  43. package/dist/behaviors/i18n.d.mts +14 -0
  44. package/dist/behaviors/i18n.mjs +28 -0
  45. package/dist/behaviors/input.mjs +16 -0
  46. package/dist/behaviors/kb-access.d.mts +2 -2
  47. package/dist/behaviors/kb-access.mjs +21 -2
  48. package/dist/behaviors/kb-acl.d.mts +2 -2
  49. package/dist/behaviors/kb-acl.mjs +14 -0
  50. package/dist/behaviors/kb-answer.d.mts +2 -2
  51. package/dist/behaviors/kb-answer.mjs +298 -23
  52. package/dist/behaviors/kb-audit.d.mts +2 -2
  53. package/dist/behaviors/kb-audit.mjs +14 -0
  54. package/dist/behaviors/kb-canvas.d.mts +3 -3
  55. package/dist/behaviors/kb-canvas.mjs +14 -0
  56. package/dist/behaviors/kb-chain.d.mts +2 -2
  57. package/dist/behaviors/kb-chain.mjs +14 -0
  58. package/dist/behaviors/kb-checkpoint.d.mts +2 -2
  59. package/dist/behaviors/kb-checkpoint.mjs +14 -0
  60. package/dist/behaviors/kb-chunks.d.mts +2 -2
  61. package/dist/behaviors/kb-chunks.mjs +14 -0
  62. package/dist/behaviors/kb-citation.d.mts +2 -2
  63. package/dist/behaviors/kb-citation.mjs +33 -6
  64. package/dist/behaviors/kb-clarify.d.mts +2 -2
  65. package/dist/behaviors/kb-clarify.mjs +14 -0
  66. package/dist/behaviors/kb-conflict.d.mts +2 -2
  67. package/dist/behaviors/kb-conflict.mjs +14 -0
  68. package/dist/behaviors/kb-connector.d.mts +2 -2
  69. package/dist/behaviors/kb-connector.mjs +14 -0
  70. package/dist/behaviors/kb-core.d.mts +1 -1
  71. package/dist/behaviors/kb-core.mjs +14 -0
  72. package/dist/behaviors/kb-eval.d.mts +2 -2
  73. package/dist/behaviors/kb-eval.mjs +21 -2
  74. package/dist/behaviors/kb-explain.d.mts +2 -2
  75. package/dist/behaviors/kb-explain.mjs +26 -2
  76. package/dist/behaviors/kb-filter.d.mts +2 -2
  77. package/dist/behaviors/kb-filter.mjs +21 -2
  78. package/dist/behaviors/kb-gap.d.mts +2 -2
  79. package/dist/behaviors/kb-gap.mjs +14 -0
  80. package/dist/behaviors/kb-hittest.d.mts +2 -2
  81. package/dist/behaviors/kb-hittest.mjs +15 -1
  82. package/dist/behaviors/kb-metadata.mjs +19 -0
  83. package/dist/behaviors/kb-passage.d.mts +2 -2
  84. package/dist/behaviors/kb-passage.mjs +14 -0
  85. package/dist/behaviors/kb-pipeline.d.mts +2 -2
  86. package/dist/behaviors/kb-pipeline.mjs +14 -0
  87. package/dist/behaviors/kb-qa.mjs +14 -0
  88. package/dist/behaviors/kb-rerank.d.mts +2 -2
  89. package/dist/behaviors/kb-rerank.mjs +14 -0
  90. package/dist/behaviors/kb-retrieval.d.mts +2 -2
  91. package/dist/behaviors/kb-retrieval.mjs +31 -4
  92. package/dist/behaviors/kb-review.d.mts +2 -2
  93. package/dist/behaviors/kb-review.mjs +21 -2
  94. package/dist/behaviors/kb-sandbox.d.mts +2 -2
  95. package/dist/behaviors/kb-sandbox.mjs +20 -1
  96. package/dist/behaviors/kb-segment.d.mts +2 -2
  97. package/dist/behaviors/kb-segment.mjs +15 -1
  98. package/dist/behaviors/kb-select.mjs +21 -2
  99. package/dist/behaviors/kb-sources.d.mts +2 -2
  100. package/dist/behaviors/kb-sources.mjs +14 -0
  101. package/dist/behaviors/kb-sql.d.mts +2 -2
  102. package/dist/behaviors/kb-sql.mjs +14 -0
  103. package/dist/behaviors/kb-trace.d.mts +2 -2
  104. package/dist/behaviors/kb-trace.mjs +22 -3
  105. package/dist/behaviors/kb-visibility.d.mts +2 -2
  106. package/dist/behaviors/kb-visibility.mjs +14 -0
  107. package/dist/behaviors/kb-who-can.d.mts +2 -2
  108. package/dist/behaviors/kb-who-can.mjs +21 -2
  109. package/dist/behaviors/mentions.d.mts +1 -1
  110. package/dist/behaviors/mentions.mjs +14 -0
  111. package/dist/behaviors/modal.d.mts +1 -1
  112. package/dist/behaviors/modal.mjs +14 -0
  113. package/dist/behaviors/motion.d.mts +16 -0
  114. package/dist/behaviors/motion.mjs +39 -0
  115. package/dist/behaviors/notification.d.mts +2 -2
  116. package/dist/behaviors/notification.mjs +27 -3
  117. package/dist/behaviors/panel.mjs +14 -0
  118. package/dist/behaviors/scroll-area.d.mts +17 -0
  119. package/dist/behaviors/scroll-area.mjs +59 -0
  120. package/dist/behaviors/select.mjs +21 -2
  121. package/dist/behaviors/slider.mjs +3 -3
  122. package/dist/behaviors/split-pane.mjs +2 -0
  123. package/dist/behaviors/suggestions.mjs +14 -0
  124. package/dist/behaviors/tabs.mjs +10 -0
  125. package/dist/behaviors/tag-input.mjs +14 -0
  126. package/dist/behaviors/theme-switcher.d.mts +2 -2
  127. package/dist/behaviors/theme-switcher.mjs +30 -2
  128. package/dist/behaviors/theme.mjs +16 -2
  129. package/dist/behaviors/toast.d.mts +2 -2
  130. package/dist/behaviors/toast.mjs +14 -0
  131. package/dist/behaviors/tree-select.mjs +27 -0
  132. package/dist/behaviors/tree.mjs +4 -4
  133. package/dist/behaviors/upload.mjs +14 -0
  134. package/dist/behaviors/url-safety.d.mts +8 -0
  135. package/dist/behaviors/url-safety.mjs +11 -0
  136. package/dist/components/accordion.d.mts +2 -0
  137. package/dist/components/ai-branch.d.mts +2 -0
  138. package/dist/components/ai-chat.css +40 -33
  139. package/dist/components/ai-chat.d.mts +2 -0
  140. package/dist/components/ai-composer.d.mts +2 -0
  141. package/dist/components/ai-context.d.mts +2 -0
  142. package/dist/components/ai-context.mjs +1 -0
  143. package/dist/components/ai-diff.css +6 -6
  144. package/dist/components/ai-diff.d.mts +2 -0
  145. package/dist/components/ai-feedback.d.mts +2 -0
  146. package/dist/components/ai-files.d.mts +2 -0
  147. package/dist/components/ai-message.d.mts +2 -0
  148. package/dist/components/ai-panel.css +12 -10
  149. package/dist/components/ai-reasoning.d.mts +2 -0
  150. package/dist/components/ai-subagent.d.mts +2 -0
  151. package/dist/components/ai-subagent.mjs +1 -0
  152. package/dist/components/ai-threads.d.mts +2 -0
  153. package/dist/components/ai-todo.d.mts +2 -0
  154. package/dist/components/ai-todo.mjs +1 -0
  155. package/dist/components/ai-tool-call.d.mts +2 -0
  156. package/dist/components/ai-tool-call.mjs +1 -0
  157. package/dist/components/ai-tool.css +16 -16
  158. package/dist/components/ai-usage.d.mts +2 -0
  159. package/dist/components/alert.d.mts +2 -0
  160. package/dist/components/autocomplete.css +12 -12
  161. package/dist/components/autocomplete.d.mts +2 -0
  162. package/dist/components/avatar.d.mts +2 -0
  163. package/dist/components/back-top.css +47 -16
  164. package/dist/components/back-top.d.mts +2 -0
  165. package/dist/components/badge.css +1 -1
  166. package/dist/components/badge.d.mts +2 -0
  167. package/dist/components/breadcrumb.css +26 -8
  168. package/dist/components/breadcrumb.d.mts +2 -0
  169. package/dist/components/btn.css +67 -62
  170. package/dist/components/btn.d.mts +2 -0
  171. package/dist/components/card.css +22 -22
  172. package/dist/components/card.d.mts +2 -0
  173. package/dist/components/carousel.d.mts +2 -0
  174. package/dist/components/cascader.css +56 -35
  175. package/dist/components/cascader.d.mts +2 -0
  176. package/dist/components/chart-area.css +46 -0
  177. package/dist/components/chart-area.d.mts +2 -0
  178. package/dist/components/chart-bar.css +66 -19
  179. package/dist/components/chart-bar.d.mts +2 -0
  180. package/dist/components/chart-calendar.d.mts +2 -0
  181. package/dist/components/chart-gauge.css +23 -3
  182. package/dist/components/chart-gauge.d.mts +2 -0
  183. package/dist/components/chart-graph.css +15 -3
  184. package/dist/components/chart-graph.d.mts +2 -0
  185. package/dist/components/chart-heatmap.css +117 -95
  186. package/dist/components/chart-heatmap.d.mts +2 -0
  187. package/dist/components/chart-line.css +54 -0
  188. package/dist/components/chart-line.d.mts +2 -0
  189. package/dist/components/chart-map.d.mts +2 -0
  190. package/dist/components/chart-pie.css +29 -0
  191. package/dist/components/chart-pie.d.mts +2 -0
  192. package/dist/components/chart-radar.css +50 -1
  193. package/dist/components/chart-radar.d.mts +2 -0
  194. package/dist/components/chart-scatter.css +39 -2
  195. package/dist/components/chart-scatter.d.mts +2 -0
  196. package/dist/components/chart-sparkline.css +20 -0
  197. package/dist/components/chart-sparkline.d.mts +2 -0
  198. package/dist/components/chart-stack.css +16 -5
  199. package/dist/components/chart-stack.d.mts +2 -0
  200. package/dist/components/charts.css +109 -35
  201. package/dist/components/charts.d.mts +2 -0
  202. package/dist/components/code-block.css +10 -8
  203. package/dist/components/code-block.d.mts +2 -0
  204. package/dist/components/command-palette.css +45 -38
  205. package/dist/components/command-palette.d.mts +2 -0
  206. package/dist/components/content.css +62 -57
  207. package/dist/components/context-menu.d.mts +2 -0
  208. package/dist/components/copy.css +10 -10
  209. package/dist/components/copy.d.mts +2 -0
  210. package/dist/components/datatable.css +72 -59
  211. package/dist/components/datatable.d.mts +2 -0
  212. package/dist/components/date-picker.css +26 -26
  213. package/dist/components/date-picker.d.mts +2 -0
  214. package/dist/components/desc.d.mts +2 -0
  215. package/dist/components/dropdown.d.mts +2 -0
  216. package/dist/components/empty.css +16 -16
  217. package/dist/components/empty.d.mts +2 -0
  218. package/dist/components/feedback.css +31 -29
  219. package/dist/components/form-validate.css +20 -6
  220. package/dist/components/form-validate.d.mts +2 -0
  221. package/dist/components/form.css +17 -14
  222. package/dist/components/form.d.mts +2 -0
  223. package/dist/components/icon.css +21 -10
  224. package/dist/components/icon.d.mts +2 -0
  225. package/dist/components/input.css +22 -22
  226. package/dist/components/input.d.mts +2 -0
  227. package/dist/components/kb-access.d.mts +2 -0
  228. package/dist/components/kb-acl.d.mts +2 -0
  229. package/dist/components/kb-agent.css +134 -55
  230. package/dist/components/kb-answer.d.mts +2 -0
  231. package/dist/components/kb-audit.d.mts +2 -0
  232. package/dist/components/kb-canvas.d.mts +2 -0
  233. package/dist/components/kb-chain.d.mts +2 -0
  234. package/dist/components/kb-checkpoint.d.mts +2 -0
  235. package/dist/components/kb-chunks.d.mts +2 -0
  236. package/dist/components/kb-citation.d.mts +2 -0
  237. package/dist/components/kb-clarify.d.mts +2 -0
  238. package/dist/components/kb-conflict.d.mts +2 -0
  239. package/dist/components/kb-connector.d.mts +2 -0
  240. package/dist/components/kb-data.css +192 -80
  241. package/dist/components/kb-eval.d.mts +2 -0
  242. package/dist/components/kb-explain.d.mts +2 -0
  243. package/dist/components/kb-filter.d.mts +2 -0
  244. package/dist/components/kb-gap.d.mts +2 -0
  245. package/dist/components/kb-ground.css +165 -73
  246. package/dist/components/kb-hittest.d.mts +2 -0
  247. package/dist/components/kb-ingest.css +308 -113
  248. package/dist/components/kb-metadata.d.mts +2 -0
  249. package/dist/components/kb-ops.css +189 -75
  250. package/dist/components/kb-passage.d.mts +2 -0
  251. package/dist/components/kb-perm.css +158 -87
  252. package/dist/components/kb-pipeline.d.mts +2 -0
  253. package/dist/components/kb-qa.d.mts +2 -0
  254. package/dist/components/kb-rerank.d.mts +2 -0
  255. package/dist/components/kb-retrieval.d.mts +2 -0
  256. package/dist/components/kb-review.d.mts +2 -0
  257. package/dist/components/kb-sandbox.d.mts +2 -0
  258. package/dist/components/kb-search.css +353 -116
  259. package/dist/components/kb-segment.d.mts +2 -0
  260. package/dist/components/kb-sources.d.mts +2 -0
  261. package/dist/components/kb-sql.d.mts +2 -0
  262. package/dist/components/kb-trace.d.mts +2 -0
  263. package/dist/components/kb-visibility.d.mts +2 -0
  264. package/dist/components/kb-who-can.d.mts +2 -0
  265. package/dist/components/kb.css +60 -36
  266. package/dist/components/kb.d.mts +2 -0
  267. package/dist/components/kbd.d.mts +2 -0
  268. package/dist/components/layout.css +10 -10
  269. package/dist/components/layout.d.mts +2 -0
  270. package/dist/components/list.d.mts +2 -0
  271. package/dist/components/material-chinese.css +10 -0
  272. package/dist/components/material-soviet.css +10 -0
  273. package/dist/components/material.css +61 -18
  274. package/dist/components/media-card.d.mts +2 -0
  275. package/dist/components/media.css +22 -27
  276. package/dist/components/mentions.css +18 -17
  277. package/dist/components/mentions.d.mts +2 -0
  278. package/dist/components/menu.css +32 -28
  279. package/dist/components/modal.css +30 -27
  280. package/dist/components/modal.d.mts +2 -0
  281. package/dist/components/nav.css +48 -44
  282. package/dist/components/nav.d.mts +2 -0
  283. package/dist/components/notification.css +31 -25
  284. package/dist/components/notification.d.mts +2 -0
  285. package/dist/components/pagination.css +15 -13
  286. package/dist/components/pagination.d.mts +2 -0
  287. package/dist/components/panel.css +19 -20
  288. package/dist/components/panel.d.mts +2 -0
  289. package/dist/components/pill.css +8 -7
  290. package/dist/components/pill.d.mts +2 -0
  291. package/dist/components/popover.css +32 -23
  292. package/dist/components/popover.d.mts +2 -0
  293. package/dist/components/progress.d.mts +2 -0
  294. package/dist/components/rating.d.mts +2 -0
  295. package/dist/components/result.d.mts +2 -0
  296. package/dist/components/scroll-area.css +84 -112
  297. package/dist/components/scroll-area.d.mts +2 -0
  298. package/dist/components/scroll-area.mjs +1 -0
  299. package/dist/components/segmented.css +47 -29
  300. package/dist/components/segmented.d.mts +2 -0
  301. package/dist/components/select.css +80 -41
  302. package/dist/components/select.d.mts +2 -0
  303. package/dist/components/sidebar.css +42 -38
  304. package/dist/components/sidebar.d.mts +2 -0
  305. package/dist/components/skeleton.d.mts +2 -0
  306. package/dist/components/slider.css +26 -22
  307. package/dist/components/slider.d.mts +2 -0
  308. package/dist/components/spinner.d.mts +2 -0
  309. package/dist/components/split-pane.css +16 -7
  310. package/dist/components/split-pane.d.mts +2 -0
  311. package/dist/components/stat.css +17 -16
  312. package/dist/components/stat.d.mts +2 -0
  313. package/dist/components/steps.css +26 -26
  314. package/dist/components/steps.d.mts +2 -0
  315. package/dist/components/switch.css +78 -39
  316. package/dist/components/switch.d.mts +2 -0
  317. package/dist/components/table.css +54 -32
  318. package/dist/components/table.d.mts +2 -0
  319. package/dist/components/tabs.css +53 -32
  320. package/dist/components/tabs.d.mts +2 -0
  321. package/dist/components/tag-input.css +12 -8
  322. package/dist/components/tag-input.d.mts +2 -0
  323. package/dist/components/tag.css +20 -12
  324. package/dist/components/tag.d.mts +2 -0
  325. package/dist/components/theme-switcher.css +7 -7
  326. package/dist/components/theme-switcher.d.mts +2 -0
  327. package/dist/components/timeline.d.mts +2 -0
  328. package/dist/components/toast.css +34 -30
  329. package/dist/components/toast.d.mts +2 -0
  330. package/dist/components/toolbar.css +19 -18
  331. package/dist/components/toolbar.d.mts +2 -0
  332. package/dist/components/tooltip.css +9 -8
  333. package/dist/components/tooltip.d.mts +2 -0
  334. package/dist/components/tree-select.css +63 -40
  335. package/dist/components/tree-select.d.mts +2 -0
  336. package/dist/components/tree.d.mts +2 -0
  337. package/dist/components/upload.css +9 -11
  338. package/dist/components/upload.d.mts +2 -0
  339. package/dist/index.d.mts +85 -84
  340. package/dist/index.mjs +14644 -13935
  341. package/dist/react.d.mts +18 -18
  342. package/dist/react.mjs +377 -36
  343. package/dist/registry.json +797 -64
  344. package/dist/tokens/colors.css +6 -0
  345. package/dist/tokens/design.css +137 -31
  346. package/dist/tokens/dtcg.json +9592 -2008
  347. package/dist/tokens/figma.variables.json +7451 -2141
  348. package/dist/tokens/motion.css +51 -0
  349. package/dist/tokens/pixel.css +10 -0
  350. package/dist/tokens/scrollbars.css +40 -0
  351. package/dist/tokens/tokens-studio.json +8390 -1142
  352. package/dist/tokens/typography.css +33 -0
  353. package/dist/tokens.css +402 -82
  354. package/dist/ui.css +4196 -2163
  355. package/dist/vue.d.mts +17 -17
  356. package/dist/vue.mjs +371 -34
  357. package/docs/spec/charts-scale-a11y.md +115 -0
  358. package/docs/spec/design-philosophy.md +5 -5
  359. package/docs/spec/design-primitives.md +10 -10
  360. package/docs/spec/motion-typography.md +105 -0
  361. package/docs/spec/scrollbars.md +46 -0
  362. package/package.json +17 -3
package/README.md CHANGED
@@ -1,19 +1,28 @@
1
1
  # @icen.ai/ui
2
2
 
3
- icen.ai 的设计系统。组件、图表和 AI 界面,共用一套设计令牌——换肤不用改代码,UI 也能直接交给模型调用。无框架,零运行时依赖,MIT。
3
+ [![npm](https://img.shields.io/npm/v/@icen.ai/ui)](https://www.npmjs.com/package/@icen.ai/ui)
4
+ [![CI](https://github.com/icen-ai/ui/actions/workflows/ci.yml/badge.svg)](https://github.com/icen-ai/ui/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
6
+
7
+ icen.ai 的设计系统:<!-- meta:components -->122<!-- /meta:components --> 个组件入口、<!-- meta:charts.total -->14<!-- /meta:charts.total --> 种图表与 AI 原生组件族共用一套设计令牌——换肤不改代码,UI 可直接交给模型调用。无框架、零运行时依赖(`dependencies` 为空,可验证)、SSR 安全,MIT。
4
8
 
5
9
  - 文档与实时演示:<https://ui.icen.ai>
6
- - 安装:`bun add @icen.ai/ui`
10
+ - 安装:`bun add @icen.ai/ui`(或 `npm i`)
7
11
 
8
12
  ## 快速开始
9
13
 
10
14
  ```css
11
- @import '@icen.ai/ui/tokens.css'; /* 设计令牌:预设 + 明暗 + 原语 */
12
- @import '@icen.ai/ui/ui.css'; /* 全部组件;也可只引 ./components/btn.css */
15
+ /* 全量一行(ui.css 已含 tokens + base + 全部组件) */
16
+ @import '@icen.ai/ui/ui.css';
17
+
18
+ /* 或按需:tokens 底座 + 单引组件 CSS
19
+ @import '@icen.ai/ui/tokens.css';
20
+ @import '@icen.ai/ui/components/btn.css'; */
13
21
  ```
14
22
 
15
23
  ```html
16
24
  <html data-icen-preset="piano" data-icen-theme="dark">
25
+ <!-- 明暗是二态契约:暗色 = 写 data-icen-theme="dark";亮色 = 移除该属性(没有 "light" 值) -->
17
26
  ```
18
27
 
19
28
  ```ts
@@ -25,75 +34,55 @@ toast.success('已保存');
25
34
  renderLine(el, { labels, series }); // 函数式组件:无 init,直接渲染
26
35
  ```
27
36
 
28
- 每个组件一页文档,页面里就是可点的真实组件:<https://ui.icen.ai/components/>
37
+ 纯静态页面走 `<link rel="stylesheet" href="./node_modules/@icen.ai/ui/ui.css">`;ES module 直引需经静态服务器(`file://` 直开会被浏览器 CORS 拦截)。更多入坑答案见 [docs/faq.md](docs/faq.md)。
29
38
 
30
39
  ## 里面有什么
31
40
 
32
- **组件。** 129 个无框架组件——表单、浮层、导航、数据展示、反馈。纯 CSS 契约加按需行为 JS:类名统一 `.icen-*`,钩子统一 `data-icen-*`,变量统一 `--icen-*`,产物全部进 cascade layers,与任何第三方 CSS 共存零冲突。
33
-
34
- **图表。** 一份 JSON,十三种图。悬停、点击、右键、图例开关全部内建,不依赖任何图表库。缩放、降采样、标记线、流式追加、PNG 导出都写在规格里。
35
-
36
- **AI 界面。** 做 AI 产品要用的零件都在:对话、推理、工具调用、审批、任务清单、用量与审计。兼容 OpenAI / Anthropic / MCP / AI SDK 四家协议,token 和成本自动记账。一行 `bindComposer` 接通输入台、流式渲染与上下文环;UI 能力可以注册成模型直接调用的工具。
37
-
38
- **知识库。** 32 个组件覆盖 RAG 全链路:摄取、检索、问数、治理、权限。引用可溯源,过程可观测,答案可验证;权限域对授权侧诚实、对受限侧沉默。
39
-
40
- **主题。** 六套预设,明暗双模,现代、中式、苏式与像素四种原语,全部挂在 `<html>` 的 data 属性上。配色与原语独立:中式推荐「东韵」,苏式推荐「巨构」,也可自由组合其它主题。自建品牌只需在自家 CSS 写一块 token 面,组件代码一行不动。
41
-
42
- **React / Vue。** 官方薄封装 `@icen.ai/ui/react`、`@icen.ai/ui/vue`:只做生命周期接线和事件桥,底下是同一个命令式内核。详见 <https://ui.icen.ai/frameworks/>
43
-
44
- 还内建:RTL 全面支持(逻辑属性 + 方向图标自动镜像,`<html dir="rtl">` 即生效)、i18n 文案注册表(683 个 key,中英双语,`setIcenLocale` 一句切换)、统一事件体系(`onIcen` 一个入口:类型化、选择器委托、策略层)、表单校验(原生约束即 schema,零新词汇)。
41
+ - **组件**:无框架组件库(表单 / 浮层 / 导航 / 数据展示 / 反馈),纯 CSS 契约 + 按需行为 JS。类名 `icen-*`、钩子 `data-icen-*`、变量 `--icen-*`,产物全部进 cascade layers,与任何第三方 CSS 共存零冲突。
42
+ - **图表**:一份 JSON <!-- meta:charts.total -->14<!-- /meta:charts.total --> 种图,悬停/点击/图例开关内建,不依赖图表库。
43
+ - **AI 界面**:对话 / 推理 / 工具调用 / 审批 / 输入台 / 用量审计;兼容 OpenAI / Anthropic / MCP / AI SDK 协议;UI 能力可注册成模型直接调用的工具。
44
+ - **知识库**:覆盖 RAG 全链路的组件族(摄取 / 检索 / 问数 / 治理 / 权限),权限域 fail-closed。
45
+ - **主题**:色彩预设 × 设计原语 × 明暗,全部挂在 `<html>` data 属性上;自建品牌只需在自家 CSS 写一块 token 面。
46
+ - **React / Vue 官方薄封装**:`@icen.ai/ui/react`、`/vue`,只做生命周期接线与事件桥;兼容契约见 [docs/frameworks.md](docs/frameworks.md)。
47
+ - **RTL 全面支持**(逻辑属性 + 方向图标自动镜像)、**i18n 文案注册表**(中英双语,`setIcenLocale` 切换)、统一事件体系(`onIcen` 一个入口)。
45
48
 
46
49
  ## 工程信任
47
50
 
48
51
  生产环境依赖一个 0.x 库之前该问的问题,直接给证据:
49
52
 
50
- - **800+ 个测试 + 10 项纪律守卫**挂在每个 PR 的 CI 上,本地 `bun run verify` 同口径。
51
- - **[SECURITY.md](https://github.com/icen-ai/ui/blob/main/SECURITY.md)**:支持版本矩阵与漏洞响应承诺;textContent-only 渲染、权限域 fail-closed、OIDC provenance 发布链,逐条可验证。
52
- - **[CONTRIBUTING.md](https://github.com/icen-ai/ui/blob/main/CONTRIBUTING.md)**:加组件的完整仪式与守卫速查表。
53
- - **[docs/versioning.md](https://github.com/icen-ai/ui/blob/main/docs/versioning.md)**:0.x 破坏性变更纪律——只在 minor 发生,CHANGELOG 逐条写明契约变化。
54
- - **发布链**:`git tag v<x.y.z>` → GitHub Actions 经 OIDC trusted publishing 发 npm,无长期 token,每次发布带 provenance 可审计。
53
+ - **全部测试与纪律守卫挂在每个 PR 的 CI 上**,本地 `bun run verify` 同口径;覆盖率地板与体积预算以棘轮文件锁在 [docs/contracts/](docs/contracts/)。
54
+ - **稳定性分层**:Core 走 1.0 冻结轨道,图表 / AI / KB 标 Beta 随 minor 演进;事实源 `docs/contracts/tiers.json`,组件页徽标同源。
55
+ - **公共契约机器快照**:`docs/contracts/snapshot.json` 六个面(类名 / 属性 / 变量 / 事件 / 导出 / 类型签名),`bun run contracts:diff` 做 tag 间对比;破坏性变更只进 minor 且 CHANGELOG 逐条公告(CI 强制联动)。
56
+ - [SECURITY.md](SECURITY.md):支持矩阵与漏洞响应承诺;textContent-only 渲染、权限域 fail-closed、URL 协议守卫,逐条有测试或守卫背书。
57
+ - [CONTRIBUTING.md](CONTRIBUTING.md):加组件的仪式与 PR 规范。
58
+ - [docs/versioning.md](docs/versioning.md):版本与稳定性政策(分层 / 0.x 纪律 / 弃用 / 主题预算 / 1.0 门槛 / 支持周期)。
59
+ - [docs/roadmap.md](docs/roadmap.md):0.11 → 0.13 → 1.0 收敛线与外部验收门槛;[docs/acceptance.md](docs/acceptance.md):AI/KB 验收矩阵;[docs/dogfood.md](docs/dogfood.md):陌生人验收记录。
60
+ - **发布链**:`git tag v<x.y.z>` → CI 全门 → OIDC trusted publishing 发 npm(provenance,无长期 token)→ 自动建 GitHub Release。
55
61
 
56
- ## 文档站
62
+ ## 消费方接入
57
63
 
58
- `site/` 是公开文档站:首页、色彩色板、129 个组件页、[知识库工作台](https://ui.icen.ai/kb/)(全真接线的成品页,导航栏实时换肤换明暗)。push 到 `main` 即由 Cloudflare Pages 自动构建(根目录 `site`,命令 `bun install && bun run build`,输出 `dist`),无需手动部署。
64
+ - **包引入(推荐)**:`bun add @icen.ai/ui`。CSS 全量一行 `./ui.css`,或按需 `./tokens.css` + `./components/<name>.css`;行为走 `./kit/<slug>` 一行入口。
65
+ - **vendor 复制**:锁定版本或离线构建场景,把 `dist/` 复制进自家仓库。
66
+ - **给 AI 代理**:包内 `AGENTS.md` + `dist/**/*.d.mts`;站点 <https://ui.icen.ai/llms.txt>(索引)与 `/llms-full.txt`(全文 + 类型面)。
59
67
 
60
- ## 构建
68
+ ## 构建与验证
61
69
 
62
70
  ```bash
63
71
  bun install
64
- bun run build # tsup + CSS 拼合 + 设计 token 导出 → dist/
65
- bun run verify # tsc + 守卫 + 800+ 测试 + 构建,四道门
72
+ bun run verify # tsc + 守卫 + 测试 + 构建 + 体积预算(CI 另跑覆盖率、包面检查与 e2e)
66
73
  ```
67
74
 
68
- 产物:`dist/tokens.css`、`dist/ui.css`、`dist/base.css`、`dist/components/*`、`dist/behaviors/*.mjs` + `.d.mts`、`dist/registry.json`、`dist/index.mjs`。
69
-
70
- ## 设计 token 导出
71
-
72
- 构建链末尾自动导出设计工具格式(`scripts/dtcg.mjs` + `scripts/design-tokens.mjs`);设计侧改 token 可走插件回灌,闭环到 `src/tokens/`:
73
-
74
- - `dist/tokens/dtcg.json`:W3C DTCG 经典版,每个预设的明暗两态各一份有效级联结果;`var()` 引用转为规范别名,文件内自洽、无环。
75
- - `dist/tokens/tokens-studio.json`:Tokens Studio 的 `$themes` 覆盖全部预设的明暗矩阵,导入后按 theme 切换即对齐 CSS。
76
- - `dist/tokens/figma.variables.json`:Figma Variables,每预设一个 collection,light / dark 双 mode;rem 按 ×16 记 px。
77
- - **诚实口径**:color-mix、box-shadow 等规范无法表示的值一律跳过并打印清单,不猜、不静默。
75
+ 产物:`dist/`(ui.css / tokens.css / base.css / components/ / behaviors/ + .d.mts / registry.json / CLI)。
76
+ 设计 token 另导出 DTCG / Tokens Studio / Figma Variables 三格式(`dist/tokens/`);规范无法表示的值明示跳过,不猜不静默。
78
77
 
79
78
  ## 目录
80
79
 
81
80
  ```
82
- src/tokens/ # 设计令牌:色彩、原语、字体(单一事实源 themes.json,改动后 bun run colors)
83
- src/base.css # 元素基线 + 6 个交互基元 + a11y 工具类
84
- src/components/ # 74 个组件 CSS
85
- src/behaviors/ # 86 个行为模块(全部 SSR 守卫 + 幂等 init + 返回销毁函数)
86
- docs/spec/ # 族规格(知识库族、设计原语…)
81
+ src/tokens/ # 设计令牌(单一事实源 themes.json,改动后 bun run colors)
82
+ src/base.css # 元素基线 + 交互基元 + a11y 工具类
83
+ src/components/ # 组件 CSS(每文件头注释 = DOM 契约)
84
+ src/behaviors/ # 行为模块(SSR 守卫 + 幂等 init + 返回销毁函数)
85
+ docs/ # 治理文档(versioning / contracts / testing / roadmap…)与 spec
87
86
  site/ # 文档站(独立 Astro 应用)
88
- scripts/ # 构建与导出脚本
87
+ scripts/ # 构建、守卫与契约工具
89
88
  ```
90
-
91
- ## 消费方接入
92
-
93
- - **包引入(推荐)**:`bun add @icen.ai/ui`,按 `./tokens.css` / `./ui.css` / `./kit/<slug>` 引入。
94
- - **vendor 复制**:锁定版本或离线构建场景,把 `dist/` 复制进自家仓库(accounts 走这条路,`bun run sync:ui`)。
95
- - **给 AI 代理**:包内 `AGENTS.md` + `dist/**/*.d.mts`;站点 <https://ui.icen.ai/llms.txt>(索引)与 `/llms-full.txt`(全文 + 类型面);场景配方 skill `icen-ui`(skill.icen.ai)。
96
-
97
- ## 现代原语
98
-
99
- 默认原语「现代」:瓷白内容面、轻盈磨砂操作层、系统排版、统一的几何节奏。想调成自己的品牌,改 `--icen-design-*` 种子即可,`createDesign` 会编译出带明暗与对比度验证的完整配方——本地文档站 `/modern/` 有实时工作台。规范与接入见 [设计原语](docs/spec/design-primitives.md)。
package/dist/base.css CHANGED
@@ -173,30 +173,60 @@ p { margin: 0 0 1em; }
173
173
  background: color-mix(in srgb, var(--icen-token-accent) 35%, transparent);
174
174
  }
175
175
 
176
- /* ══════════════════════════════════════════════════════════════
177
- 全局滚动条:细薄、无上下箭头、贴 token
178
- 尺寸/轨道/滑块/按钮全部走 --icen-token-scrollbar-*,
179
- 每个色彩预设可在自己的块里覆盖以表达品牌色差。
180
- ══════════════════════════════════════════════════════════════ */
181
-
182
- ::-webkit-scrollbar { width: var(--icen-token-scrollbar-size); height: var(--icen-token-scrollbar-size); }
176
+ /* Native scrollbar paint; policy slots live in tokens/scrollbars.css. */
177
+ * {
178
+ scrollbar-width: var(--icen-scrollbar-width, thin);
179
+ scrollbar-color: color-mix(in srgb, var(--icen-token-scrollbar-thumb) var(--icen-scrollbar-idle, 100%), transparent) var(--icen-token-scrollbar-track);
180
+ scrollbar-gutter: var(--icen-token-scrollbar-gutter);
181
+ }
182
+ :where(*:not(html):hover) {
183
+ scrollbar-color: color-mix(in srgb, var(--icen-token-scrollbar-thumb-hover) var(--icen-scrollbar-hover, 100%), transparent) var(--icen-token-scrollbar-track);
184
+ }
185
+ :where(*:focus-within, [data-icen-scroll-active]) {
186
+ scrollbar-color: color-mix(in srgb, var(--icen-token-scrollbar-thumb) var(--icen-scrollbar-active, 100%), transparent) var(--icen-token-scrollbar-track);
187
+ }
188
+ /* Keep native arrows disabled when standard properties would override the skin. */
189
+ @supports selector(::-webkit-scrollbar) {
190
+ *, :where(*:hover, *:focus-within, [data-icen-scroll-active]) {
191
+ scrollbar-width: var(--icen-scrollbar-webkit-width, auto);
192
+ scrollbar-color: auto;
193
+ }
194
+ }
195
+ ::-webkit-scrollbar {
196
+ width: var(--icen-token-scrollbar-size);
197
+ height: var(--icen-token-scrollbar-size);
198
+ display: var(--icen-scrollbar-display, block);
199
+ }
183
200
  ::-webkit-scrollbar-track { background: var(--icen-token-scrollbar-track); }
184
201
  ::-webkit-scrollbar-thumb {
185
- background: var(--icen-token-scrollbar-thumb);
202
+ background-color: color-mix(in srgb, var(--icen-token-scrollbar-thumb) var(--icen-scrollbar-idle, 100%), transparent);
186
203
  border-radius: var(--icen-radius-full);
187
- border: 2px solid transparent;
204
+ border: var(--icen-token-scrollbar-inset) solid transparent;
188
205
  background-clip: padding-box;
206
+ transition: background-color var(--icen-token-scrollbar-fade-duration) var(--icen-ease-out);
189
207
  }
190
- ::-webkit-scrollbar-thumb:hover {
191
- background: var(--icen-token-scrollbar-thumb-hover);
192
- background-clip: padding-box;
208
+ :where(*:not(html):hover)::-webkit-scrollbar-thumb {
209
+ background-color: color-mix(in srgb, var(--icen-token-scrollbar-thumb-hover) var(--icen-scrollbar-hover, 100%), transparent);
210
+ }
211
+ :is(*:focus-within, [data-icen-scroll-active])::-webkit-scrollbar-thumb {
212
+ background-color: color-mix(in srgb, var(--icen-token-scrollbar-thumb) var(--icen-scrollbar-active, 100%), transparent);
213
+ }
214
+ ::-webkit-scrollbar-thumb:active {
215
+ background-color: color-mix(in srgb, var(--icen-token-scrollbar-thumb) var(--icen-scrollbar-active, 100%), transparent);
193
216
  }
194
217
  ::-webkit-scrollbar-button,
195
218
  ::-webkit-scrollbar-button:single-button { display: none; width: 0; height: 0; }
196
219
  ::-webkit-scrollbar-corner { background: transparent; }
197
- html {
198
- scrollbar-width: thin;
199
- scrollbar-color: var(--icen-token-scrollbar-thumb) var(--icen-token-scrollbar-track);
220
+ @media (prefers-reduced-motion: reduce) {
221
+ ::-webkit-scrollbar-thumb { transition: none; }
222
+ }
223
+ @media (forced-colors: active) {
224
+ *, :where(*:hover, *:focus-within, [data-icen-scroll-active]) {
225
+ scrollbar-width: auto;
226
+ scrollbar-color: auto;
227
+ }
228
+ ::-webkit-scrollbar { display: block; }
229
+ ::-webkit-scrollbar-thumb { background-color: CanvasText; }
200
230
  }
201
231
 
202
232
  /* ══════════════════════════════════════════════════════════════
@@ -266,7 +296,7 @@ html {
266
296
  --icen-z-raised: 10;
267
297
  --icen-z-sticky: 50;
268
298
  --icen-z-chrome: 200;
269
- --icen-z-toast: 400;
299
+ --icen-z-toast: 560; /* 反馈层必须压过 dialog(500):模态里触发的 toast 不能被遮罩压住;仍低于 popover/tooltip(瞬态浮层) */
270
300
  --icen-z-banner: 450;
271
301
  --icen-z-dialog: 500;
272
302
  --icen-z-popover: 620;
@@ -289,6 +319,10 @@ html {
289
319
  @keyframes icen-spin {
290
320
  to { transform: rotate(360deg); }
291
321
  }
322
+ @keyframes icen-blink {
323
+ 0%, 100% { opacity: 1; }
324
+ 50% { opacity: 0; }
325
+ }
292
326
  @keyframes icen-skeleton-pulse {
293
327
  0%, 100% { opacity: 1; }
294
328
  50% { opacity: 0.5; }
@@ -459,15 +493,20 @@ html {
459
493
  动效与无障碍兜底
460
494
  ══════════════════════════════════════════════════════════════ */
461
495
 
462
- /* reduced-motion:所有动画/过渡压缩到 ~0ms(保留可访问性最小可视信号) */
496
+ /* reduced-motion 与手动覆盖的行为压制(变量开关在 design.css 自持):
497
+ 循环/环境动画单次瞬时 + 平滑滚动关闭;过渡时长经 --icen-design-motion 归零瞬时完成(状态反馈保留)。 */
463
498
  @media (prefers-reduced-motion: reduce) {
464
- *, *::before, *::after {
465
- animation-duration: 0.01ms !important; /* guard:allow-important 动效守卫:降级媒体查询需压制组件动画 */
466
- animation-iteration-count: 1 !important; /* guard:allow-important 动效守卫:降级媒体查询需压制组件动画 */
467
- transition-duration: 0.01ms !important; /* guard:allow-important 动效守卫:降级媒体查询需压制组件动画 */
468
- scroll-behavior: auto !important; /* guard:allow-important 动效守卫:降级媒体查询需压制组件动画 */
499
+ :root:not([data-icen-motion="full"]) *, :root:not([data-icen-motion="full"]) *::before, :root:not([data-icen-motion="full"]) *::after {
500
+ animation-duration: 0.01ms !important; /* guard:allow-important 动效守卫:降级需压制组件循环动画 */
501
+ animation-iteration-count: 1 !important; /* guard:allow-important 动效守卫:降级需压制组件循环动画 */
502
+ scroll-behavior: auto !important; /* guard:allow-important 动效守卫:降级需关闭平滑滚动 */
469
503
  }
470
504
  }
505
+ :root[data-icen-motion="reduce"] *, :root[data-icen-motion="reduce"] *::before, :root[data-icen-motion="reduce"] *::after {
506
+ animation-duration: 0.01ms !important; /* guard:allow-important 手动降级:与系统降级同口径 */
507
+ animation-iteration-count: 1 !important; /* guard:allow-important 手动降级:与系统降级同口径 */
508
+ scroll-behavior: auto !important; /* guard:allow-important 手动降级:与系统降级同口径 */
509
+ }
471
510
 
472
511
  /* 触屏设备(无精细指针):压低 pressable 按下形变(hover 抬升已由 hover 守卫关闭) */
473
512
  @media (pointer: coarse) {
@@ -484,6 +523,13 @@ html {
484
523
  outline: 2px solid Highlight;
485
524
  outline-offset: 2px;
486
525
  }
526
+ /* 强制颜色会丢弃 box-shadow——本库多数焦点环正是 box-shadow(var(--icen-ring))。
527
+ 组件普遍以 outline:none 关闭了默认焦点,所以这里必须提权兜底:
528
+ 所有原生可聚焦元素在强制颜色下都拿到系统焦点框。 */
529
+ :where(a[href], button, input, select, textarea, summary, [tabindex]:not([tabindex='-1'])):focus-visible {
530
+ outline: 2px solid Highlight !important; /* guard:allow-important 高对比模式焦点兜底:组件 outline:none 会吞掉默认焦点 */
531
+ outline-offset: 2px !important; /* guard:allow-important 高对比模式焦点兜底:组件 outline:none 会吞掉默认焦点 */
532
+ }
487
533
  }
488
534
 
489
535
  /* 打印:去掉阴影/背景/动效,节约墨水并避免打印截断 */
@@ -21,7 +21,7 @@ function setup(container) {
21
21
  if (item.classList.contains("is-disabled")) return;
22
22
  const open = item.classList.toggle("is-open");
23
23
  trigger.setAttribute("aria-expanded", String(open));
24
- const index = Array.from(container.querySelectorAll(".icen-accordion-item")).indexOf(item);
24
+ const index = Array.from(container.querySelectorAll(":scope > .icen-accordion-item")).indexOf(item);
25
25
  emitIcen(container, "icen:accordion-toggle", { item, index, open });
26
26
  if (single && open) {
27
27
  container.querySelectorAll(".icen-accordion-item.is-open").forEach((other) => {
@@ -122,6 +122,7 @@ var ICEN_LOCALE_ZH_CN = {
122
122
  "aiChat.roleTool": "\u5DE5",
123
123
  "aiChat.roleUser": "\u6211",
124
124
  "aiChat.thinking": "\u601D\u8003 {p0}",
125
+ "aiChat.thinkingNow": "\u6B63\u5728\u601D\u8003\u2026",
125
126
  /* ── aiComposer ── */
126
127
  "aiComposer.attachAgent": "\u4EE3\u7406",
127
128
  "aiComposer.library": "\u6DFB\u52A0\u4E0A\u4E0B\u6587\u4E0E\u547D\u4EE4",
@@ -304,6 +305,19 @@ var ICEN_LOCALE_ZH_CN = {
304
305
  "charts.noData": "\u6682\u65E0\u6570\u636E",
305
306
  "charts.resetZoom": "\u91CD\u7F6E\u7F29\u653E",
306
307
  "charts.sampled": "\u5DF2\u62BD\u6837 {p0}\u2192{p1} \u70B9",
308
+ "charts.a11y.keyboardHint": "\u65B9\u5411\u952E\u9010\u70B9\u6D4F\u89C8\uFF0CHome/End \u8DF3\u9996\u5C3E\uFF0CEsc \u91CD\u7F6E\u7F29\u653E",
309
+ "charts.a11y.max": "\u6700\u5927 {p0}\uFF08{p1}\uFF09",
310
+ "charts.a11y.min": "\u6700\u5C0F {p0}\uFF08{p1}\uFF09",
311
+ "charts.a11y.point": "{p0}\uFF1A{p1}\uFF0C\u7B2C {p2}/{p3} \u70B9",
312
+ "charts.a11y.pointSeries": "{p0} \xB7 {p1}\uFF1A{p2}\uFF0C\u7B2C {p3}/{p4} \u70B9",
313
+ "charts.a11y.range": "\u7C7B\u76EE {p0} \u81F3 {p1}",
314
+ "charts.a11y.scatterRange": "x \u8303\u56F4 {p0}\u2013{p1}\uFF0Cy \u8303\u56F4 {p2}\u2013{p3}",
315
+ "charts.a11y.seriesCount": "{p0} \u4E2A\u7CFB\u5217",
316
+ "charts.a11y.tableCaption": "{p0} \u6570\u636E\u8868",
317
+ "charts.a11y.trend": "\u9996\u7CFB\u5217\u8D8B\u52BF{p0}",
318
+ "charts.a11y.trendDown": "\u4E0B\u964D",
319
+ "charts.a11y.trendFlat": "\u6301\u5E73",
320
+ "charts.a11y.trendUp": "\u4E0A\u5347",
307
321
  "charts.type.area": "\u9762\u79EF\u56FE",
308
322
  "charts.type.donut": "\u73AF\u5F62\u56FE",
309
323
  "charts.type.gauge": "\u4EEA\u8868\u76D8",
@@ -1,4 +1,4 @@
1
- import { AiContent, AiContentPart } from './ai-core.js';
1
+ import { AiContent, AiContentPart } from './ai-core.mjs';
2
2
 
3
3
  /**
4
4
  * 初始化 root 下的全部 .icen-ai-chat(root 自身匹配时包含自身)。
@@ -46,7 +46,7 @@ interface AiMessageHandle {
46
46
  /** 在正文末开一个流式文本节点,返回 createAiStream 句柄 */
47
47
  stream(): AiStreamHandle;
48
48
  }
49
- /** 单个内容部件 → DOM 节点(文本一律 textContent;媒体 src 走 aiContentUrl 归一)。
49
+ /** 单个内容部件 → DOM 节点(文本一律 textContent;媒体 src 走 aiContentUrl 归一 + safeUrl 守卫)。
50
50
  * 导出供 ai-tool 的 IO 区复用(MCP 多模态回执;类名 .ai-msg-* 语义即「消息部件」,全局生效)。
51
51
  * 前置条件:浏览器环境——本函数无 SSR 守卫,SSR 由调用方守卫覆盖(renderAiMessage /
52
52
  * renderAiToolCall 头部守卫后才会触达此处)。 */
@@ -21,6 +21,7 @@ var ICEN_LOCALE_ZH_CN = {
21
21
  "aiChat.roleTool": "\u5DE5",
22
22
  "aiChat.roleUser": "\u6211",
23
23
  "aiChat.thinking": "\u601D\u8003 {p0}",
24
+ "aiChat.thinkingNow": "\u6B63\u5728\u601D\u8003\u2026",
24
25
  /* ── aiComposer ── */
25
26
  "aiComposer.attachAgent": "\u4EE3\u7406",
26
27
  "aiComposer.library": "\u6DFB\u52A0\u4E0A\u4E0B\u6587\u4E0E\u547D\u4EE4",
@@ -203,6 +204,19 @@ var ICEN_LOCALE_ZH_CN = {
203
204
  "charts.noData": "\u6682\u65E0\u6570\u636E",
204
205
  "charts.resetZoom": "\u91CD\u7F6E\u7F29\u653E",
205
206
  "charts.sampled": "\u5DF2\u62BD\u6837 {p0}\u2192{p1} \u70B9",
207
+ "charts.a11y.keyboardHint": "\u65B9\u5411\u952E\u9010\u70B9\u6D4F\u89C8\uFF0CHome/End \u8DF3\u9996\u5C3E\uFF0CEsc \u91CD\u7F6E\u7F29\u653E",
208
+ "charts.a11y.max": "\u6700\u5927 {p0}\uFF08{p1}\uFF09",
209
+ "charts.a11y.min": "\u6700\u5C0F {p0}\uFF08{p1}\uFF09",
210
+ "charts.a11y.point": "{p0}\uFF1A{p1}\uFF0C\u7B2C {p2}/{p3} \u70B9",
211
+ "charts.a11y.pointSeries": "{p0} \xB7 {p1}\uFF1A{p2}\uFF0C\u7B2C {p3}/{p4} \u70B9",
212
+ "charts.a11y.range": "\u7C7B\u76EE {p0} \u81F3 {p1}",
213
+ "charts.a11y.scatterRange": "x \u8303\u56F4 {p0}\u2013{p1}\uFF0Cy \u8303\u56F4 {p2}\u2013{p3}",
214
+ "charts.a11y.seriesCount": "{p0} \u4E2A\u7CFB\u5217",
215
+ "charts.a11y.tableCaption": "{p0} \u6570\u636E\u8868",
216
+ "charts.a11y.trend": "\u9996\u7CFB\u5217\u8D8B\u52BF{p0}",
217
+ "charts.a11y.trendDown": "\u4E0B\u964D",
218
+ "charts.a11y.trendFlat": "\u6301\u5E73",
219
+ "charts.a11y.trendUp": "\u4E0A\u5347",
206
220
  "charts.type.area": "\u9762\u79EF\u56FE",
207
221
  "charts.type.donut": "\u73AF\u5F62\u56FE",
208
222
  "charts.type.gauge": "\u4EEA\u8868\u76D8",
@@ -1102,6 +1116,15 @@ function svgIcon(svg) {
1102
1116
  return parseSvg(svg);
1103
1117
  }
1104
1118
 
1119
+ // src/behaviors/url-safety.ts
1120
+ function safeUrl(uri) {
1121
+ const s = uri.replace(/[\t\n\r\0-\x1f\x7f]/g, "").trim().toLowerCase();
1122
+ if (!s) return void 0;
1123
+ if (s.startsWith("javascript:") || s.startsWith("vbscript:")) return void 0;
1124
+ if (/^data:\s*text\/html/.test(s)) return void 0;
1125
+ return uri;
1126
+ }
1127
+
1105
1128
  // src/behaviors/ai-chat.ts
1106
1129
  var ICON_COPY = iconHTML("copy");
1107
1130
  var ICON_RETRY = iconHTML("refresh");
@@ -1140,6 +1163,10 @@ function setupChat(chat) {
1140
1163
  if (el.__icenAiChatInit) return void 0;
1141
1164
  el.__icenAiChatInit = true;
1142
1165
  const scrollEl = chat.querySelector(".icen-ai-chat-scroll");
1166
+ if (scrollEl && !scrollEl.hasAttribute("aria-live")) {
1167
+ scrollEl.setAttribute("role", "log");
1168
+ scrollEl.setAttribute("aria-live", "polite");
1169
+ }
1143
1170
  let jump = chat.querySelector(".icen-ai-chat-jump") ?? null;
1144
1171
  let pinned = true;
1145
1172
  let unread = 0;
@@ -1196,6 +1223,26 @@ function setupChat(chat) {
1196
1223
  updateJump();
1197
1224
  }
1198
1225
  };
1226
+ const onUserLeaveIntent = () => {
1227
+ if (!pinned) return;
1228
+ pinned = false;
1229
+ updateJump();
1230
+ };
1231
+ const onWheel = (e) => {
1232
+ if (e.deltaY < 0) onUserLeaveIntent();
1233
+ };
1234
+ let lastTouchY = 0;
1235
+ const onTouchStart = (e) => {
1236
+ lastTouchY = e.touches[0]?.clientY ?? 0;
1237
+ };
1238
+ const onTouchMove = (e) => {
1239
+ const y = e.touches[0]?.clientY ?? 0;
1240
+ if (y > lastTouchY + 4) onUserLeaveIntent();
1241
+ lastTouchY = y;
1242
+ };
1243
+ const onScrollerKeydown = (e) => {
1244
+ if (e.key === "PageUp" || e.key === "Home" || e.key === "ArrowUp") onUserLeaveIntent();
1245
+ };
1199
1246
  const onJumpClick = () => {
1200
1247
  pinned = true;
1201
1248
  unread = 0;
@@ -1251,10 +1298,18 @@ function setupChat(chat) {
1251
1298
  }
1252
1299
  lastMsgCount = scrollEl.querySelectorAll(".icen-ai-msg").length;
1253
1300
  scrollEl.addEventListener("scroll", onScroll, { passive: true });
1301
+ scrollEl.addEventListener("wheel", onWheel, { passive: true });
1302
+ scrollEl.addEventListener("touchstart", onTouchStart, { passive: true });
1303
+ scrollEl.addEventListener("touchmove", onTouchMove, { passive: true });
1304
+ scrollEl.addEventListener("keydown", onScrollerKeydown);
1254
1305
  jump.addEventListener("click", onJumpClick);
1255
1306
  const jumpBtn = jump;
1256
1307
  disposers.push(() => {
1257
1308
  scrollEl.removeEventListener("scroll", onScroll);
1309
+ scrollEl.removeEventListener("wheel", onWheel);
1310
+ scrollEl.removeEventListener("touchstart", onTouchStart);
1311
+ scrollEl.removeEventListener("touchmove", onTouchMove);
1312
+ scrollEl.removeEventListener("keydown", onScrollerKeydown);
1258
1313
  jumpBtn.removeEventListener("click", onJumpClick);
1259
1314
  });
1260
1315
  if (typeof ResizeObserver !== "undefined") {
@@ -1326,6 +1381,8 @@ function createAiStream(el) {
1326
1381
  el.setAttribute("aria-busy", "true");
1327
1382
  if (reasoning) {
1328
1383
  reasoning.classList.add("is-streaming");
1384
+ const timeEl = reasoning.querySelector(".icen-ai-reasoning-time");
1385
+ if (timeEl) timeEl.textContent = t("aiChat.thinkingNow");
1329
1386
  setReasoningOpen(reasoning, true);
1330
1387
  const head = reasoning.querySelector(".icen-ai-reasoning-head");
1331
1388
  if (head && head.dataset.icenReasoningStart == null) head.dataset.icenReasoningStart = String(startedAt);
@@ -1413,10 +1470,9 @@ var ROLE_AVATAR = {
1413
1470
  system: t("aiChat.roleSystem"),
1414
1471
  tool: t("aiChat.roleTool")
1415
1472
  };
1416
- function safeHref(uri) {
1417
- const s = uri.trim().toLowerCase();
1418
- if (s.startsWith("javascript:") || s.startsWith("data:text/html")) return void 0;
1419
- return uri;
1473
+ function safeContentUrl(part) {
1474
+ const src = aiContentUrl(part);
1475
+ return src ? safeUrl(src) : void 0;
1420
1476
  }
1421
1477
  function renderAiContentPart(part) {
1422
1478
  if (part.type === "text") {
@@ -1426,7 +1482,7 @@ function renderAiContentPart(part) {
1426
1482
  return span;
1427
1483
  }
1428
1484
  if (part.type === "image") {
1429
- const src = aiContentUrl(part);
1485
+ const src = safeContentUrl(part);
1430
1486
  if (!src) return void 0;
1431
1487
  const img = document.createElement("img");
1432
1488
  img.className = "icen-ai-msg-media icen-ai-msg-media--image";
@@ -1436,7 +1492,7 @@ function renderAiContentPart(part) {
1436
1492
  return img;
1437
1493
  }
1438
1494
  if (part.type === "audio") {
1439
- const src = aiContentUrl(part);
1495
+ const src = safeContentUrl(part);
1440
1496
  if (!src) return void 0;
1441
1497
  const audio = document.createElement("audio");
1442
1498
  audio.className = "icen-ai-msg-media icen-ai-msg-media--audio";
@@ -1445,7 +1501,7 @@ function renderAiContentPart(part) {
1445
1501
  return audio;
1446
1502
  }
1447
1503
  if (part.type === "video") {
1448
- const src = aiContentUrl(part);
1504
+ const src = safeContentUrl(part);
1449
1505
  if (!src) return void 0;
1450
1506
  const video = document.createElement("video");
1451
1507
  video.className = "icen-ai-msg-media icen-ai-msg-media--video";
@@ -1457,7 +1513,7 @@ function renderAiContentPart(part) {
1457
1513
  if (part.type === "file") {
1458
1514
  const chip = h("span", "icen-ai-msg-file");
1459
1515
  chip.appendChild(h("span", "ai-msg-file-name", part.filename ?? t("aiChat.file")));
1460
- const href2 = part.url ? safeHref(part.url) : void 0;
1516
+ const href2 = part.url ? safeUrl(part.url) : void 0;
1461
1517
  if (href2 && /^(https?:|data:)/i.test(href2)) {
1462
1518
  const a2 = document.createElement("a");
1463
1519
  a2.className = "icen-ai-msg-file-link";
@@ -1470,7 +1526,7 @@ function renderAiContentPart(part) {
1470
1526
  }
1471
1527
  const a = document.createElement("a");
1472
1528
  a.className = "icen-ai-msg-link";
1473
- const href = safeHref(part.uri);
1529
+ const href = safeUrl(part.uri);
1474
1530
  if (href) a.href = href;
1475
1531
  a.textContent = part.name ?? part.uri;
1476
1532
  a.title = part.uri;
@@ -1532,7 +1588,7 @@ function renderAiMessage(scrollEl, model) {
1532
1588
  errEl.textContent = message;
1533
1589
  },
1534
1590
  stream() {
1535
- const span = h("span", "ai-msg-stream-text");
1591
+ const span = h("span", "icen-ai-msg-text ai-msg-stream-text");
1536
1592
  body.appendChild(span);
1537
1593
  return createAiStream(span);
1538
1594
  }
@@ -1,8 +1,8 @@
1
- import { IconName } from './icons.js';
2
- import { AiContent, AiUsage } from './ai-core.js';
3
- import { PanelSizing } from './popover.js';
4
- import { AiTodoItem, AiUsageRingOpts } from './ai-panel.js';
5
- import { AiClient, AiAuditor } from './ai-provider.js';
1
+ import { IconName } from './icons.mjs';
2
+ import { AiContent, AiUsage } from './ai-core.mjs';
3
+ import { PanelSizing } from './popover.mjs';
4
+ import { AiTodoItem, AiUsageRingOpts } from './ai-panel.mjs';
5
+ import { AiClient, AiAuditor } from './ai-provider.mjs';
6
6
 
7
7
  interface AiProviderModelOption {
8
8
  id: string;
@@ -122,6 +122,7 @@ var ICEN_LOCALE_ZH_CN = {
122
122
  "aiChat.roleTool": "\u5DE5",
123
123
  "aiChat.roleUser": "\u6211",
124
124
  "aiChat.thinking": "\u601D\u8003 {p0}",
125
+ "aiChat.thinkingNow": "\u6B63\u5728\u601D\u8003\u2026",
125
126
  /* ── aiComposer ── */
126
127
  "aiComposer.attachAgent": "\u4EE3\u7406",
127
128
  "aiComposer.library": "\u6DFB\u52A0\u4E0A\u4E0B\u6587\u4E0E\u547D\u4EE4",
@@ -304,6 +305,19 @@ var ICEN_LOCALE_ZH_CN = {
304
305
  "charts.noData": "\u6682\u65E0\u6570\u636E",
305
306
  "charts.resetZoom": "\u91CD\u7F6E\u7F29\u653E",
306
307
  "charts.sampled": "\u5DF2\u62BD\u6837 {p0}\u2192{p1} \u70B9",
308
+ "charts.a11y.keyboardHint": "\u65B9\u5411\u952E\u9010\u70B9\u6D4F\u89C8\uFF0CHome/End \u8DF3\u9996\u5C3E\uFF0CEsc \u91CD\u7F6E\u7F29\u653E",
309
+ "charts.a11y.max": "\u6700\u5927 {p0}\uFF08{p1}\uFF09",
310
+ "charts.a11y.min": "\u6700\u5C0F {p0}\uFF08{p1}\uFF09",
311
+ "charts.a11y.point": "{p0}\uFF1A{p1}\uFF0C\u7B2C {p2}/{p3} \u70B9",
312
+ "charts.a11y.pointSeries": "{p0} \xB7 {p1}\uFF1A{p2}\uFF0C\u7B2C {p3}/{p4} \u70B9",
313
+ "charts.a11y.range": "\u7C7B\u76EE {p0} \u81F3 {p1}",
314
+ "charts.a11y.scatterRange": "x \u8303\u56F4 {p0}\u2013{p1}\uFF0Cy \u8303\u56F4 {p2}\u2013{p3}",
315
+ "charts.a11y.seriesCount": "{p0} \u4E2A\u7CFB\u5217",
316
+ "charts.a11y.tableCaption": "{p0} \u6570\u636E\u8868",
317
+ "charts.a11y.trend": "\u9996\u7CFB\u5217\u8D8B\u52BF{p0}",
318
+ "charts.a11y.trendDown": "\u4E0B\u964D",
319
+ "charts.a11y.trendFlat": "\u6301\u5E73",
320
+ "charts.a11y.trendUp": "\u4E0A\u5347",
307
321
  "charts.type.area": "\u9762\u79EF\u56FE",
308
322
  "charts.type.donut": "\u73AF\u5F62\u56FE",
309
323
  "charts.type.gauge": "\u4EEA\u8868\u76D8",
@@ -1687,6 +1701,15 @@ var ICON_SPARKLES = svgWrap(
1687
1701
  '<path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3z"/>'
1688
1702
  );
1689
1703
 
1704
+ // src/behaviors/url-safety.ts
1705
+ function safeUrl(uri) {
1706
+ const s = uri.replace(/[\t\n\r\0-\x1f\x7f]/g, "").trim().toLowerCase();
1707
+ if (!s) return void 0;
1708
+ if (s.startsWith("javascript:") || s.startsWith("vbscript:")) return void 0;
1709
+ if (/^data:\s*text\/html/.test(s)) return void 0;
1710
+ return uri;
1711
+ }
1712
+
1690
1713
  // src/behaviors/ai-chat.ts
1691
1714
  var ICON_COPY = iconHTML("copy");
1692
1715
  var ICON_RETRY = iconHTML("refresh");
@@ -1714,6 +1737,8 @@ function createAiStream(el) {
1714
1737
  el.setAttribute("aria-busy", "true");
1715
1738
  if (reasoning) {
1716
1739
  reasoning.classList.add("is-streaming");
1740
+ const timeEl = reasoning.querySelector(".icen-ai-reasoning-time");
1741
+ if (timeEl) timeEl.textContent = t("aiChat.thinkingNow");
1717
1742
  setReasoningOpen(reasoning, true);
1718
1743
  const head = reasoning.querySelector(".icen-ai-reasoning-head");
1719
1744
  if (head && head.dataset.icenReasoningStart == null) head.dataset.icenReasoningStart = String(startedAt);
@@ -1801,10 +1826,9 @@ var ROLE_AVATAR = {
1801
1826
  system: t("aiChat.roleSystem"),
1802
1827
  tool: t("aiChat.roleTool")
1803
1828
  };
1804
- function safeHref(uri) {
1805
- const s = uri.trim().toLowerCase();
1806
- if (s.startsWith("javascript:") || s.startsWith("data:text/html")) return void 0;
1807
- return uri;
1829
+ function safeContentUrl(part) {
1830
+ const src = aiContentUrl(part);
1831
+ return src ? safeUrl(src) : void 0;
1808
1832
  }
1809
1833
  function renderAiContentPart(part) {
1810
1834
  if (part.type === "text") {
@@ -1814,7 +1838,7 @@ function renderAiContentPart(part) {
1814
1838
  return span;
1815
1839
  }
1816
1840
  if (part.type === "image") {
1817
- const src = aiContentUrl(part);
1841
+ const src = safeContentUrl(part);
1818
1842
  if (!src) return void 0;
1819
1843
  const img = document.createElement("img");
1820
1844
  img.className = "icen-ai-msg-media icen-ai-msg-media--image";
@@ -1824,7 +1848,7 @@ function renderAiContentPart(part) {
1824
1848
  return img;
1825
1849
  }
1826
1850
  if (part.type === "audio") {
1827
- const src = aiContentUrl(part);
1851
+ const src = safeContentUrl(part);
1828
1852
  if (!src) return void 0;
1829
1853
  const audio = document.createElement("audio");
1830
1854
  audio.className = "icen-ai-msg-media icen-ai-msg-media--audio";
@@ -1833,7 +1857,7 @@ function renderAiContentPart(part) {
1833
1857
  return audio;
1834
1858
  }
1835
1859
  if (part.type === "video") {
1836
- const src = aiContentUrl(part);
1860
+ const src = safeContentUrl(part);
1837
1861
  if (!src) return void 0;
1838
1862
  const video = document.createElement("video");
1839
1863
  video.className = "icen-ai-msg-media icen-ai-msg-media--video";
@@ -1845,7 +1869,7 @@ function renderAiContentPart(part) {
1845
1869
  if (part.type === "file") {
1846
1870
  const chip = h("span", "icen-ai-msg-file");
1847
1871
  chip.appendChild(h("span", "ai-msg-file-name", part.filename ?? t("aiChat.file")));
1848
- const href2 = part.url ? safeHref(part.url) : void 0;
1872
+ const href2 = part.url ? safeUrl(part.url) : void 0;
1849
1873
  if (href2 && /^(https?:|data:)/i.test(href2)) {
1850
1874
  const a2 = document.createElement("a");
1851
1875
  a2.className = "icen-ai-msg-file-link";
@@ -1858,7 +1882,7 @@ function renderAiContentPart(part) {
1858
1882
  }
1859
1883
  const a = document.createElement("a");
1860
1884
  a.className = "icen-ai-msg-link";
1861
- const href = safeHref(part.uri);
1885
+ const href = safeUrl(part.uri);
1862
1886
  if (href) a.href = href;
1863
1887
  a.textContent = part.name ?? part.uri;
1864
1888
  a.title = part.uri;
@@ -1920,7 +1944,7 @@ function renderAiMessage(scrollEl, model) {
1920
1944
  errEl.textContent = message;
1921
1945
  },
1922
1946
  stream() {
1923
- const span = h("span", "ai-msg-stream-text");
1947
+ const span = h("span", "icen-ai-msg-text ai-msg-stream-text");
1924
1948
  body.appendChild(span);
1925
1949
  return createAiStream(span);
1926
1950
  }