@icen.ai/ui 0.10.0 → 0.11.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.
- package/AGENTS.md +79 -309
- package/LICENSE +21 -0
- package/README.md +43 -54
- package/dist/base.css +57 -16
- package/dist/behaviors/accordion.mjs +1 -1
- package/dist/behaviors/ai-branch.mjs +1 -0
- package/dist/behaviors/ai-chat.d.mts +2 -2
- package/dist/behaviors/ai-chat.mjs +53 -10
- package/dist/behaviors/ai-composer.d.mts +5 -5
- package/dist/behaviors/ai-composer.mjs +21 -10
- package/dist/behaviors/ai-core.mjs +1 -0
- package/dist/behaviors/ai-diff.mjs +1 -0
- package/dist/behaviors/ai-feedback.mjs +1 -0
- package/dist/behaviors/ai-panel.d.mts +2 -2
- package/dist/behaviors/ai-panel.mjs +1 -0
- package/dist/behaviors/ai-provider.d.mts +1 -1
- package/dist/behaviors/ai-provider.mjs +1 -0
- package/dist/behaviors/ai-threads.mjs +1 -0
- package/dist/behaviors/ai-tool.d.mts +1 -1
- package/dist/behaviors/ai-tool.mjs +57 -24
- package/dist/behaviors/ai-tools.mjs +937 -28
- package/dist/behaviors/autocomplete.d.mts +1 -1
- package/dist/behaviors/autocomplete.mjs +1 -0
- package/dist/behaviors/back-top.mjs +1 -0
- package/dist/behaviors/carousel.mjs +8 -1
- package/dist/behaviors/cascader.mjs +14 -0
- package/dist/behaviors/charts.mjs +56 -20
- package/dist/behaviors/code-block.mjs +9 -1
- package/dist/behaviors/command-palette.mjs +3 -3
- package/dist/behaviors/context-menu.d.mts +2 -2
- package/dist/behaviors/context-menu.mjs +1 -0
- package/dist/behaviors/controls.d.mts +8 -8
- package/dist/behaviors/controls.mjs +37 -6
- package/dist/behaviors/copy.mjs +1 -0
- package/dist/behaviors/datatable.d.mts +3 -3
- package/dist/behaviors/datatable.mjs +76 -11
- package/dist/behaviors/date-picker.mjs +1 -0
- package/dist/behaviors/events.d.mts +10 -10
- package/dist/behaviors/events.mjs +11 -1
- package/dist/behaviors/form-validate.d.mts +10 -1
- package/dist/behaviors/form-validate.mjs +266 -4611
- package/dist/behaviors/i18n.d.mts +1 -0
- package/dist/behaviors/i18n.mjs +2 -0
- package/dist/behaviors/input.mjs +3 -0
- package/dist/behaviors/kb-access.d.mts +2 -2
- package/dist/behaviors/kb-access.mjs +8 -2
- package/dist/behaviors/kb-acl.d.mts +2 -2
- package/dist/behaviors/kb-acl.mjs +1 -0
- package/dist/behaviors/kb-answer.d.mts +2 -2
- package/dist/behaviors/kb-answer.mjs +56 -23
- package/dist/behaviors/kb-audit.d.mts +2 -2
- package/dist/behaviors/kb-audit.mjs +1 -0
- package/dist/behaviors/kb-canvas.d.mts +3 -3
- package/dist/behaviors/kb-canvas.mjs +1 -0
- package/dist/behaviors/kb-chain.d.mts +2 -2
- package/dist/behaviors/kb-chain.mjs +1 -0
- package/dist/behaviors/kb-checkpoint.d.mts +2 -2
- package/dist/behaviors/kb-checkpoint.mjs +1 -0
- package/dist/behaviors/kb-chunks.d.mts +2 -2
- package/dist/behaviors/kb-chunks.mjs +1 -0
- package/dist/behaviors/kb-citation.d.mts +2 -2
- package/dist/behaviors/kb-citation.mjs +20 -6
- package/dist/behaviors/kb-clarify.d.mts +2 -2
- package/dist/behaviors/kb-clarify.mjs +1 -0
- package/dist/behaviors/kb-conflict.d.mts +2 -2
- package/dist/behaviors/kb-conflict.mjs +1 -0
- package/dist/behaviors/kb-connector.d.mts +2 -2
- package/dist/behaviors/kb-connector.mjs +1 -0
- package/dist/behaviors/kb-core.d.mts +1 -1
- package/dist/behaviors/kb-core.mjs +1 -0
- package/dist/behaviors/kb-eval.d.mts +2 -2
- package/dist/behaviors/kb-eval.mjs +8 -2
- package/dist/behaviors/kb-explain.d.mts +2 -2
- package/dist/behaviors/kb-explain.mjs +13 -2
- package/dist/behaviors/kb-filter.d.mts +2 -2
- package/dist/behaviors/kb-filter.mjs +8 -2
- package/dist/behaviors/kb-gap.d.mts +2 -2
- package/dist/behaviors/kb-gap.mjs +1 -0
- package/dist/behaviors/kb-hittest.d.mts +2 -2
- package/dist/behaviors/kb-hittest.mjs +2 -1
- package/dist/behaviors/kb-metadata.mjs +6 -0
- package/dist/behaviors/kb-passage.d.mts +2 -2
- package/dist/behaviors/kb-passage.mjs +1 -0
- package/dist/behaviors/kb-pipeline.d.mts +2 -2
- package/dist/behaviors/kb-pipeline.mjs +1 -0
- package/dist/behaviors/kb-qa.mjs +1 -0
- package/dist/behaviors/kb-rerank.d.mts +2 -2
- package/dist/behaviors/kb-rerank.mjs +1 -0
- package/dist/behaviors/kb-retrieval.d.mts +2 -2
- package/dist/behaviors/kb-retrieval.mjs +18 -4
- package/dist/behaviors/kb-review.d.mts +2 -2
- package/dist/behaviors/kb-review.mjs +8 -2
- package/dist/behaviors/kb-sandbox.d.mts +2 -2
- package/dist/behaviors/kb-sandbox.mjs +7 -1
- package/dist/behaviors/kb-segment.d.mts +2 -2
- package/dist/behaviors/kb-segment.mjs +2 -1
- package/dist/behaviors/kb-select.mjs +8 -2
- package/dist/behaviors/kb-sources.d.mts +2 -2
- package/dist/behaviors/kb-sources.mjs +1 -0
- package/dist/behaviors/kb-sql.d.mts +2 -2
- package/dist/behaviors/kb-sql.mjs +1 -0
- package/dist/behaviors/kb-trace.d.mts +2 -2
- package/dist/behaviors/kb-trace.mjs +9 -3
- package/dist/behaviors/kb-visibility.d.mts +2 -2
- package/dist/behaviors/kb-visibility.mjs +1 -0
- package/dist/behaviors/kb-who-can.d.mts +2 -2
- package/dist/behaviors/kb-who-can.mjs +8 -2
- package/dist/behaviors/mentions.d.mts +1 -1
- package/dist/behaviors/mentions.mjs +1 -0
- package/dist/behaviors/modal.d.mts +1 -1
- package/dist/behaviors/modal.mjs +1 -0
- package/dist/behaviors/notification.d.mts +2 -2
- package/dist/behaviors/notification.mjs +14 -3
- package/dist/behaviors/panel.mjs +1 -0
- package/dist/behaviors/scroll-area.d.mts +17 -0
- package/dist/behaviors/scroll-area.mjs +59 -0
- package/dist/behaviors/select.mjs +8 -2
- package/dist/behaviors/slider.mjs +3 -3
- package/dist/behaviors/split-pane.mjs +2 -0
- package/dist/behaviors/suggestions.mjs +1 -0
- package/dist/behaviors/tabs.mjs +10 -0
- package/dist/behaviors/tag-input.mjs +1 -0
- package/dist/behaviors/theme-switcher.d.mts +2 -2
- package/dist/behaviors/theme-switcher.mjs +17 -2
- package/dist/behaviors/theme.mjs +16 -2
- package/dist/behaviors/toast.d.mts +2 -2
- package/dist/behaviors/toast.mjs +1 -0
- package/dist/behaviors/tree-select.mjs +14 -0
- package/dist/behaviors/tree.mjs +4 -4
- package/dist/behaviors/upload.mjs +1 -0
- package/dist/behaviors/url-safety.d.mts +8 -0
- package/dist/behaviors/url-safety.mjs +11 -0
- package/dist/components/accordion.d.mts +2 -0
- package/dist/components/ai-branch.d.mts +2 -0
- package/dist/components/ai-chat.css +40 -33
- package/dist/components/ai-chat.d.mts +2 -0
- package/dist/components/ai-composer.d.mts +2 -0
- package/dist/components/ai-context.d.mts +2 -0
- package/dist/components/ai-context.mjs +1 -0
- package/dist/components/ai-diff.css +6 -6
- package/dist/components/ai-diff.d.mts +2 -0
- package/dist/components/ai-feedback.d.mts +2 -0
- package/dist/components/ai-files.d.mts +2 -0
- package/dist/components/ai-message.d.mts +2 -0
- package/dist/components/ai-panel.css +12 -10
- package/dist/components/ai-reasoning.d.mts +2 -0
- package/dist/components/ai-subagent.d.mts +2 -0
- package/dist/components/ai-subagent.mjs +1 -0
- package/dist/components/ai-threads.d.mts +2 -0
- package/dist/components/ai-todo.d.mts +2 -0
- package/dist/components/ai-todo.mjs +1 -0
- package/dist/components/ai-tool-call.d.mts +2 -0
- package/dist/components/ai-tool-call.mjs +1 -0
- package/dist/components/ai-tool.css +16 -16
- package/dist/components/ai-usage.d.mts +2 -0
- package/dist/components/alert.d.mts +2 -0
- package/dist/components/autocomplete.css +12 -12
- package/dist/components/autocomplete.d.mts +2 -0
- package/dist/components/avatar.d.mts +2 -0
- package/dist/components/back-top.css +47 -16
- package/dist/components/back-top.d.mts +2 -0
- package/dist/components/badge.css +1 -1
- package/dist/components/badge.d.mts +2 -0
- package/dist/components/breadcrumb.css +26 -8
- package/dist/components/breadcrumb.d.mts +2 -0
- package/dist/components/btn.css +67 -62
- package/dist/components/btn.d.mts +2 -0
- package/dist/components/card.css +22 -22
- package/dist/components/card.d.mts +2 -0
- package/dist/components/carousel.d.mts +2 -0
- package/dist/components/cascader.css +56 -35
- package/dist/components/cascader.d.mts +2 -0
- package/dist/components/chart-area.css +46 -0
- package/dist/components/chart-area.d.mts +2 -0
- package/dist/components/chart-bar.css +66 -19
- package/dist/components/chart-bar.d.mts +2 -0
- package/dist/components/chart-calendar.d.mts +2 -0
- package/dist/components/chart-gauge.css +23 -3
- package/dist/components/chart-gauge.d.mts +2 -0
- package/dist/components/chart-graph.css +15 -3
- package/dist/components/chart-graph.d.mts +2 -0
- package/dist/components/chart-heatmap.css +117 -95
- package/dist/components/chart-heatmap.d.mts +2 -0
- package/dist/components/chart-line.css +54 -0
- package/dist/components/chart-line.d.mts +2 -0
- package/dist/components/chart-map.d.mts +2 -0
- package/dist/components/chart-pie.css +29 -0
- package/dist/components/chart-pie.d.mts +2 -0
- package/dist/components/chart-radar.css +50 -1
- package/dist/components/chart-radar.d.mts +2 -0
- package/dist/components/chart-scatter.css +39 -2
- package/dist/components/chart-scatter.d.mts +2 -0
- package/dist/components/chart-sparkline.css +20 -0
- package/dist/components/chart-sparkline.d.mts +2 -0
- package/dist/components/chart-stack.css +16 -5
- package/dist/components/chart-stack.d.mts +2 -0
- package/dist/components/charts.css +80 -35
- package/dist/components/charts.d.mts +2 -0
- package/dist/components/code-block.css +10 -8
- package/dist/components/code-block.d.mts +2 -0
- package/dist/components/command-palette.css +45 -38
- package/dist/components/command-palette.d.mts +2 -0
- package/dist/components/content.css +62 -57
- package/dist/components/context-menu.d.mts +2 -0
- package/dist/components/copy.css +10 -10
- package/dist/components/copy.d.mts +2 -0
- package/dist/components/datatable.css +72 -59
- package/dist/components/datatable.d.mts +2 -0
- package/dist/components/date-picker.css +26 -26
- package/dist/components/date-picker.d.mts +2 -0
- package/dist/components/desc.d.mts +2 -0
- package/dist/components/dropdown.d.mts +2 -0
- package/dist/components/empty.css +16 -16
- package/dist/components/empty.d.mts +2 -0
- package/dist/components/feedback.css +31 -29
- package/dist/components/form-validate.css +20 -6
- package/dist/components/form-validate.d.mts +2 -0
- package/dist/components/form.css +17 -14
- package/dist/components/form.d.mts +2 -0
- package/dist/components/icon.css +21 -10
- package/dist/components/icon.d.mts +2 -0
- package/dist/components/input.css +22 -22
- package/dist/components/input.d.mts +2 -0
- package/dist/components/kb-access.d.mts +2 -0
- package/dist/components/kb-acl.d.mts +2 -0
- package/dist/components/kb-agent.css +134 -55
- package/dist/components/kb-answer.d.mts +2 -0
- package/dist/components/kb-audit.d.mts +2 -0
- package/dist/components/kb-canvas.d.mts +2 -0
- package/dist/components/kb-chain.d.mts +2 -0
- package/dist/components/kb-checkpoint.d.mts +2 -0
- package/dist/components/kb-chunks.d.mts +2 -0
- package/dist/components/kb-citation.d.mts +2 -0
- package/dist/components/kb-clarify.d.mts +2 -0
- package/dist/components/kb-conflict.d.mts +2 -0
- package/dist/components/kb-connector.d.mts +2 -0
- package/dist/components/kb-data.css +192 -80
- package/dist/components/kb-eval.d.mts +2 -0
- package/dist/components/kb-explain.d.mts +2 -0
- package/dist/components/kb-filter.d.mts +2 -0
- package/dist/components/kb-gap.d.mts +2 -0
- package/dist/components/kb-ground.css +165 -73
- package/dist/components/kb-hittest.d.mts +2 -0
- package/dist/components/kb-ingest.css +308 -113
- package/dist/components/kb-metadata.d.mts +2 -0
- package/dist/components/kb-ops.css +189 -75
- package/dist/components/kb-passage.d.mts +2 -0
- package/dist/components/kb-perm.css +158 -87
- package/dist/components/kb-pipeline.d.mts +2 -0
- package/dist/components/kb-qa.d.mts +2 -0
- package/dist/components/kb-rerank.d.mts +2 -0
- package/dist/components/kb-retrieval.d.mts +2 -0
- package/dist/components/kb-review.d.mts +2 -0
- package/dist/components/kb-sandbox.d.mts +2 -0
- package/dist/components/kb-search.css +353 -116
- package/dist/components/kb-segment.d.mts +2 -0
- package/dist/components/kb-sources.d.mts +2 -0
- package/dist/components/kb-sql.d.mts +2 -0
- package/dist/components/kb-trace.d.mts +2 -0
- package/dist/components/kb-visibility.d.mts +2 -0
- package/dist/components/kb-who-can.d.mts +2 -0
- package/dist/components/kb.css +60 -36
- package/dist/components/kb.d.mts +2 -0
- package/dist/components/kbd.d.mts +2 -0
- package/dist/components/layout.css +10 -10
- package/dist/components/layout.d.mts +2 -0
- package/dist/components/list.d.mts +2 -0
- package/dist/components/material-chinese.css +10 -0
- package/dist/components/material-soviet.css +10 -0
- package/dist/components/material.css +61 -18
- package/dist/components/media-card.d.mts +2 -0
- package/dist/components/media.css +22 -27
- package/dist/components/mentions.css +18 -17
- package/dist/components/mentions.d.mts +2 -0
- package/dist/components/menu.css +32 -28
- package/dist/components/modal.css +30 -27
- package/dist/components/modal.d.mts +2 -0
- package/dist/components/nav.css +48 -44
- package/dist/components/nav.d.mts +2 -0
- package/dist/components/notification.css +31 -25
- package/dist/components/notification.d.mts +2 -0
- package/dist/components/pagination.css +15 -13
- package/dist/components/pagination.d.mts +2 -0
- package/dist/components/panel.css +19 -20
- package/dist/components/panel.d.mts +2 -0
- package/dist/components/pill.css +8 -7
- package/dist/components/pill.d.mts +2 -0
- package/dist/components/popover.css +32 -23
- package/dist/components/popover.d.mts +2 -0
- package/dist/components/progress.d.mts +2 -0
- package/dist/components/rating.d.mts +2 -0
- package/dist/components/result.d.mts +2 -0
- package/dist/components/scroll-area.css +84 -112
- package/dist/components/scroll-area.d.mts +2 -0
- package/dist/components/scroll-area.mjs +1 -0
- package/dist/components/segmented.css +47 -29
- package/dist/components/segmented.d.mts +2 -0
- package/dist/components/select.css +80 -41
- package/dist/components/select.d.mts +2 -0
- package/dist/components/sidebar.css +42 -38
- package/dist/components/sidebar.d.mts +2 -0
- package/dist/components/skeleton.d.mts +2 -0
- package/dist/components/slider.css +26 -22
- package/dist/components/slider.d.mts +2 -0
- package/dist/components/spinner.d.mts +2 -0
- package/dist/components/split-pane.css +16 -7
- package/dist/components/split-pane.d.mts +2 -0
- package/dist/components/stat.css +17 -16
- package/dist/components/stat.d.mts +2 -0
- package/dist/components/steps.css +26 -26
- package/dist/components/steps.d.mts +2 -0
- package/dist/components/switch.css +78 -39
- package/dist/components/switch.d.mts +2 -0
- package/dist/components/table.css +54 -32
- package/dist/components/table.d.mts +2 -0
- package/dist/components/tabs.css +53 -32
- package/dist/components/tabs.d.mts +2 -0
- package/dist/components/tag-input.css +12 -8
- package/dist/components/tag-input.d.mts +2 -0
- package/dist/components/tag.css +20 -12
- package/dist/components/tag.d.mts +2 -0
- package/dist/components/theme-switcher.css +7 -7
- package/dist/components/theme-switcher.d.mts +2 -0
- package/dist/components/timeline.d.mts +2 -0
- package/dist/components/toast.css +34 -30
- package/dist/components/toast.d.mts +2 -0
- package/dist/components/toolbar.css +19 -18
- package/dist/components/toolbar.d.mts +2 -0
- package/dist/components/tooltip.css +9 -8
- package/dist/components/tooltip.d.mts +2 -0
- package/dist/components/tree-select.css +63 -40
- package/dist/components/tree-select.d.mts +2 -0
- package/dist/components/tree.d.mts +2 -0
- package/dist/components/upload.css +9 -11
- package/dist/components/upload.d.mts +2 -0
- package/dist/index.d.mts +85 -84
- package/dist/index.mjs +14764 -14311
- package/dist/react.d.mts +18 -18
- package/dist/react.mjs +135 -36
- package/dist/registry.json +795 -64
- package/dist/tokens/colors.css +6 -0
- package/dist/tokens/design.css +130 -30
- package/dist/tokens/dtcg.json +6189 -1461
- package/dist/tokens/figma.variables.json +4107 -345
- package/dist/tokens/pixel.css +3 -0
- package/dist/tokens/scrollbars.css +40 -0
- package/dist/tokens/tokens-studio.json +5191 -271
- package/dist/tokens.css +304 -81
- package/dist/ui.css +4050 -2148
- package/dist/vue.d.mts +17 -17
- package/dist/vue.mjs +129 -34
- package/docs/spec/design-philosophy.md +5 -5
- package/docs/spec/design-primitives.md +10 -10
- package/docs/spec/scrollbars.md +46 -0
- package/package.json +16 -3
package/README.md
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
# @icen.ai/ui
|
|
2
2
|
|
|
3
|
-
icen.ai
|
|
3
|
+
[](https://www.npmjs.com/package/@icen.ai/ui)
|
|
4
|
+
[](https://github.com/icen-ai/ui/actions/workflows/ci.yml)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
icen.ai 的设计系统。组件、图表和 AI 界面共用一套设计令牌——换肤不改代码,UI 可直接交给模型调用。无框架,零运行时依赖,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
|
-
|
|
12
|
-
@import '@icen.ai/ui/ui.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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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 十三种图,悬停/点击/图例开关内建,不依赖图表库。
|
|
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
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
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
|
-
|
|
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
|
|
65
|
-
bun run verify # tsc + 守卫 + 800+ 测试 + 构建,四道门
|
|
72
|
+
bun run verify # tsc + 守卫 + 测试 + 构建 + 体积预算(CI 另跑覆盖率、包面检查与 e2e)
|
|
66
73
|
```
|
|
67
74
|
|
|
68
|
-
产物:`dist
|
|
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/ #
|
|
83
|
-
src/base.css # 元素基线 +
|
|
84
|
-
src/components/ #
|
|
85
|
-
src/behaviors/ #
|
|
86
|
-
docs/
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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:
|
|
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
|
|
191
|
-
background: var(--icen-token-scrollbar-thumb-hover);
|
|
192
|
-
|
|
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
|
-
|
|
198
|
-
scrollbar-
|
|
199
|
-
|
|
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:
|
|
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; }
|
|
@@ -484,6 +518,13 @@ html {
|
|
|
484
518
|
outline: 2px solid Highlight;
|
|
485
519
|
outline-offset: 2px;
|
|
486
520
|
}
|
|
521
|
+
/* 强制颜色会丢弃 box-shadow——本库多数焦点环正是 box-shadow(var(--icen-ring))。
|
|
522
|
+
组件普遍以 outline:none 关闭了默认焦点,所以这里必须提权兜底:
|
|
523
|
+
所有原生可聚焦元素在强制颜色下都拿到系统焦点框。 */
|
|
524
|
+
:where(a[href], button, input, select, textarea, summary, [tabindex]:not([tabindex='-1'])):focus-visible {
|
|
525
|
+
outline: 2px solid Highlight !important; /* guard:allow-important 高对比模式焦点兜底:组件 outline:none 会吞掉默认焦点 */
|
|
526
|
+
outline-offset: 2px !important; /* guard:allow-important 高对比模式焦点兜底:组件 outline:none 会吞掉默认焦点 */
|
|
527
|
+
}
|
|
487
528
|
}
|
|
488
529
|
|
|
489
530
|
/* 打印:去掉阴影/背景/动效,节约墨水并避免打印截断 */
|
|
@@ -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",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AiContent, AiContentPart } from './ai-core.
|
|
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",
|
|
@@ -1102,6 +1103,15 @@ function svgIcon(svg) {
|
|
|
1102
1103
|
return parseSvg(svg);
|
|
1103
1104
|
}
|
|
1104
1105
|
|
|
1106
|
+
// src/behaviors/url-safety.ts
|
|
1107
|
+
function safeUrl(uri) {
|
|
1108
|
+
const s = uri.replace(/[\t\n\r\0-\x1f\x7f]/g, "").trim().toLowerCase();
|
|
1109
|
+
if (!s) return void 0;
|
|
1110
|
+
if (s.startsWith("javascript:") || s.startsWith("vbscript:")) return void 0;
|
|
1111
|
+
if (/^data:\s*text\/html/.test(s)) return void 0;
|
|
1112
|
+
return uri;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1105
1115
|
// src/behaviors/ai-chat.ts
|
|
1106
1116
|
var ICON_COPY = iconHTML("copy");
|
|
1107
1117
|
var ICON_RETRY = iconHTML("refresh");
|
|
@@ -1140,6 +1150,10 @@ function setupChat(chat) {
|
|
|
1140
1150
|
if (el.__icenAiChatInit) return void 0;
|
|
1141
1151
|
el.__icenAiChatInit = true;
|
|
1142
1152
|
const scrollEl = chat.querySelector(".icen-ai-chat-scroll");
|
|
1153
|
+
if (scrollEl && !scrollEl.hasAttribute("aria-live")) {
|
|
1154
|
+
scrollEl.setAttribute("role", "log");
|
|
1155
|
+
scrollEl.setAttribute("aria-live", "polite");
|
|
1156
|
+
}
|
|
1143
1157
|
let jump = chat.querySelector(".icen-ai-chat-jump") ?? null;
|
|
1144
1158
|
let pinned = true;
|
|
1145
1159
|
let unread = 0;
|
|
@@ -1196,6 +1210,26 @@ function setupChat(chat) {
|
|
|
1196
1210
|
updateJump();
|
|
1197
1211
|
}
|
|
1198
1212
|
};
|
|
1213
|
+
const onUserLeaveIntent = () => {
|
|
1214
|
+
if (!pinned) return;
|
|
1215
|
+
pinned = false;
|
|
1216
|
+
updateJump();
|
|
1217
|
+
};
|
|
1218
|
+
const onWheel = (e) => {
|
|
1219
|
+
if (e.deltaY < 0) onUserLeaveIntent();
|
|
1220
|
+
};
|
|
1221
|
+
let lastTouchY = 0;
|
|
1222
|
+
const onTouchStart = (e) => {
|
|
1223
|
+
lastTouchY = e.touches[0]?.clientY ?? 0;
|
|
1224
|
+
};
|
|
1225
|
+
const onTouchMove = (e) => {
|
|
1226
|
+
const y = e.touches[0]?.clientY ?? 0;
|
|
1227
|
+
if (y > lastTouchY + 4) onUserLeaveIntent();
|
|
1228
|
+
lastTouchY = y;
|
|
1229
|
+
};
|
|
1230
|
+
const onScrollerKeydown = (e) => {
|
|
1231
|
+
if (e.key === "PageUp" || e.key === "Home" || e.key === "ArrowUp") onUserLeaveIntent();
|
|
1232
|
+
};
|
|
1199
1233
|
const onJumpClick = () => {
|
|
1200
1234
|
pinned = true;
|
|
1201
1235
|
unread = 0;
|
|
@@ -1251,10 +1285,18 @@ function setupChat(chat) {
|
|
|
1251
1285
|
}
|
|
1252
1286
|
lastMsgCount = scrollEl.querySelectorAll(".icen-ai-msg").length;
|
|
1253
1287
|
scrollEl.addEventListener("scroll", onScroll, { passive: true });
|
|
1288
|
+
scrollEl.addEventListener("wheel", onWheel, { passive: true });
|
|
1289
|
+
scrollEl.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
1290
|
+
scrollEl.addEventListener("touchmove", onTouchMove, { passive: true });
|
|
1291
|
+
scrollEl.addEventListener("keydown", onScrollerKeydown);
|
|
1254
1292
|
jump.addEventListener("click", onJumpClick);
|
|
1255
1293
|
const jumpBtn = jump;
|
|
1256
1294
|
disposers.push(() => {
|
|
1257
1295
|
scrollEl.removeEventListener("scroll", onScroll);
|
|
1296
|
+
scrollEl.removeEventListener("wheel", onWheel);
|
|
1297
|
+
scrollEl.removeEventListener("touchstart", onTouchStart);
|
|
1298
|
+
scrollEl.removeEventListener("touchmove", onTouchMove);
|
|
1299
|
+
scrollEl.removeEventListener("keydown", onScrollerKeydown);
|
|
1258
1300
|
jumpBtn.removeEventListener("click", onJumpClick);
|
|
1259
1301
|
});
|
|
1260
1302
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -1326,6 +1368,8 @@ function createAiStream(el) {
|
|
|
1326
1368
|
el.setAttribute("aria-busy", "true");
|
|
1327
1369
|
if (reasoning) {
|
|
1328
1370
|
reasoning.classList.add("is-streaming");
|
|
1371
|
+
const timeEl = reasoning.querySelector(".icen-ai-reasoning-time");
|
|
1372
|
+
if (timeEl) timeEl.textContent = t("aiChat.thinkingNow");
|
|
1329
1373
|
setReasoningOpen(reasoning, true);
|
|
1330
1374
|
const head = reasoning.querySelector(".icen-ai-reasoning-head");
|
|
1331
1375
|
if (head && head.dataset.icenReasoningStart == null) head.dataset.icenReasoningStart = String(startedAt);
|
|
@@ -1413,10 +1457,9 @@ var ROLE_AVATAR = {
|
|
|
1413
1457
|
system: t("aiChat.roleSystem"),
|
|
1414
1458
|
tool: t("aiChat.roleTool")
|
|
1415
1459
|
};
|
|
1416
|
-
function
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1419
|
-
return uri;
|
|
1460
|
+
function safeContentUrl(part) {
|
|
1461
|
+
const src = aiContentUrl(part);
|
|
1462
|
+
return src ? safeUrl(src) : void 0;
|
|
1420
1463
|
}
|
|
1421
1464
|
function renderAiContentPart(part) {
|
|
1422
1465
|
if (part.type === "text") {
|
|
@@ -1426,7 +1469,7 @@ function renderAiContentPart(part) {
|
|
|
1426
1469
|
return span;
|
|
1427
1470
|
}
|
|
1428
1471
|
if (part.type === "image") {
|
|
1429
|
-
const src =
|
|
1472
|
+
const src = safeContentUrl(part);
|
|
1430
1473
|
if (!src) return void 0;
|
|
1431
1474
|
const img = document.createElement("img");
|
|
1432
1475
|
img.className = "icen-ai-msg-media icen-ai-msg-media--image";
|
|
@@ -1436,7 +1479,7 @@ function renderAiContentPart(part) {
|
|
|
1436
1479
|
return img;
|
|
1437
1480
|
}
|
|
1438
1481
|
if (part.type === "audio") {
|
|
1439
|
-
const src =
|
|
1482
|
+
const src = safeContentUrl(part);
|
|
1440
1483
|
if (!src) return void 0;
|
|
1441
1484
|
const audio = document.createElement("audio");
|
|
1442
1485
|
audio.className = "icen-ai-msg-media icen-ai-msg-media--audio";
|
|
@@ -1445,7 +1488,7 @@ function renderAiContentPart(part) {
|
|
|
1445
1488
|
return audio;
|
|
1446
1489
|
}
|
|
1447
1490
|
if (part.type === "video") {
|
|
1448
|
-
const src =
|
|
1491
|
+
const src = safeContentUrl(part);
|
|
1449
1492
|
if (!src) return void 0;
|
|
1450
1493
|
const video = document.createElement("video");
|
|
1451
1494
|
video.className = "icen-ai-msg-media icen-ai-msg-media--video";
|
|
@@ -1457,7 +1500,7 @@ function renderAiContentPart(part) {
|
|
|
1457
1500
|
if (part.type === "file") {
|
|
1458
1501
|
const chip = h("span", "icen-ai-msg-file");
|
|
1459
1502
|
chip.appendChild(h("span", "ai-msg-file-name", part.filename ?? t("aiChat.file")));
|
|
1460
|
-
const href2 = part.url ?
|
|
1503
|
+
const href2 = part.url ? safeUrl(part.url) : void 0;
|
|
1461
1504
|
if (href2 && /^(https?:|data:)/i.test(href2)) {
|
|
1462
1505
|
const a2 = document.createElement("a");
|
|
1463
1506
|
a2.className = "icen-ai-msg-file-link";
|
|
@@ -1470,7 +1513,7 @@ function renderAiContentPart(part) {
|
|
|
1470
1513
|
}
|
|
1471
1514
|
const a = document.createElement("a");
|
|
1472
1515
|
a.className = "icen-ai-msg-link";
|
|
1473
|
-
const href =
|
|
1516
|
+
const href = safeUrl(part.uri);
|
|
1474
1517
|
if (href) a.href = href;
|
|
1475
1518
|
a.textContent = part.name ?? part.uri;
|
|
1476
1519
|
a.title = part.uri;
|
|
@@ -1532,7 +1575,7 @@ function renderAiMessage(scrollEl, model) {
|
|
|
1532
1575
|
errEl.textContent = message;
|
|
1533
1576
|
},
|
|
1534
1577
|
stream() {
|
|
1535
|
-
const span = h("span", "ai-msg-stream-text");
|
|
1578
|
+
const span = h("span", "icen-ai-msg-text ai-msg-stream-text");
|
|
1536
1579
|
body.appendChild(span);
|
|
1537
1580
|
return createAiStream(span);
|
|
1538
1581
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { IconName } from './icons.
|
|
2
|
-
import { AiContent, AiUsage } from './ai-core.
|
|
3
|
-
import { PanelSizing } from './popover.
|
|
4
|
-
import { AiTodoItem, AiUsageRingOpts } from './ai-panel.
|
|
5
|
-
import { AiClient, AiAuditor } from './ai-provider.
|
|
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",
|
|
@@ -1687,6 +1688,15 @@ var ICON_SPARKLES = svgWrap(
|
|
|
1687
1688
|
'<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
1689
|
);
|
|
1689
1690
|
|
|
1691
|
+
// src/behaviors/url-safety.ts
|
|
1692
|
+
function safeUrl(uri) {
|
|
1693
|
+
const s = uri.replace(/[\t\n\r\0-\x1f\x7f]/g, "").trim().toLowerCase();
|
|
1694
|
+
if (!s) return void 0;
|
|
1695
|
+
if (s.startsWith("javascript:") || s.startsWith("vbscript:")) return void 0;
|
|
1696
|
+
if (/^data:\s*text\/html/.test(s)) return void 0;
|
|
1697
|
+
return uri;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1690
1700
|
// src/behaviors/ai-chat.ts
|
|
1691
1701
|
var ICON_COPY = iconHTML("copy");
|
|
1692
1702
|
var ICON_RETRY = iconHTML("refresh");
|
|
@@ -1714,6 +1724,8 @@ function createAiStream(el) {
|
|
|
1714
1724
|
el.setAttribute("aria-busy", "true");
|
|
1715
1725
|
if (reasoning) {
|
|
1716
1726
|
reasoning.classList.add("is-streaming");
|
|
1727
|
+
const timeEl = reasoning.querySelector(".icen-ai-reasoning-time");
|
|
1728
|
+
if (timeEl) timeEl.textContent = t("aiChat.thinkingNow");
|
|
1717
1729
|
setReasoningOpen(reasoning, true);
|
|
1718
1730
|
const head = reasoning.querySelector(".icen-ai-reasoning-head");
|
|
1719
1731
|
if (head && head.dataset.icenReasoningStart == null) head.dataset.icenReasoningStart = String(startedAt);
|
|
@@ -1801,10 +1813,9 @@ var ROLE_AVATAR = {
|
|
|
1801
1813
|
system: t("aiChat.roleSystem"),
|
|
1802
1814
|
tool: t("aiChat.roleTool")
|
|
1803
1815
|
};
|
|
1804
|
-
function
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
return uri;
|
|
1816
|
+
function safeContentUrl(part) {
|
|
1817
|
+
const src = aiContentUrl(part);
|
|
1818
|
+
return src ? safeUrl(src) : void 0;
|
|
1808
1819
|
}
|
|
1809
1820
|
function renderAiContentPart(part) {
|
|
1810
1821
|
if (part.type === "text") {
|
|
@@ -1814,7 +1825,7 @@ function renderAiContentPart(part) {
|
|
|
1814
1825
|
return span;
|
|
1815
1826
|
}
|
|
1816
1827
|
if (part.type === "image") {
|
|
1817
|
-
const src =
|
|
1828
|
+
const src = safeContentUrl(part);
|
|
1818
1829
|
if (!src) return void 0;
|
|
1819
1830
|
const img = document.createElement("img");
|
|
1820
1831
|
img.className = "icen-ai-msg-media icen-ai-msg-media--image";
|
|
@@ -1824,7 +1835,7 @@ function renderAiContentPart(part) {
|
|
|
1824
1835
|
return img;
|
|
1825
1836
|
}
|
|
1826
1837
|
if (part.type === "audio") {
|
|
1827
|
-
const src =
|
|
1838
|
+
const src = safeContentUrl(part);
|
|
1828
1839
|
if (!src) return void 0;
|
|
1829
1840
|
const audio = document.createElement("audio");
|
|
1830
1841
|
audio.className = "icen-ai-msg-media icen-ai-msg-media--audio";
|
|
@@ -1833,7 +1844,7 @@ function renderAiContentPart(part) {
|
|
|
1833
1844
|
return audio;
|
|
1834
1845
|
}
|
|
1835
1846
|
if (part.type === "video") {
|
|
1836
|
-
const src =
|
|
1847
|
+
const src = safeContentUrl(part);
|
|
1837
1848
|
if (!src) return void 0;
|
|
1838
1849
|
const video = document.createElement("video");
|
|
1839
1850
|
video.className = "icen-ai-msg-media icen-ai-msg-media--video";
|
|
@@ -1845,7 +1856,7 @@ function renderAiContentPart(part) {
|
|
|
1845
1856
|
if (part.type === "file") {
|
|
1846
1857
|
const chip = h("span", "icen-ai-msg-file");
|
|
1847
1858
|
chip.appendChild(h("span", "ai-msg-file-name", part.filename ?? t("aiChat.file")));
|
|
1848
|
-
const href2 = part.url ?
|
|
1859
|
+
const href2 = part.url ? safeUrl(part.url) : void 0;
|
|
1849
1860
|
if (href2 && /^(https?:|data:)/i.test(href2)) {
|
|
1850
1861
|
const a2 = document.createElement("a");
|
|
1851
1862
|
a2.className = "icen-ai-msg-file-link";
|
|
@@ -1858,7 +1869,7 @@ function renderAiContentPart(part) {
|
|
|
1858
1869
|
}
|
|
1859
1870
|
const a = document.createElement("a");
|
|
1860
1871
|
a.className = "icen-ai-msg-link";
|
|
1861
|
-
const href =
|
|
1872
|
+
const href = safeUrl(part.uri);
|
|
1862
1873
|
if (href) a.href = href;
|
|
1863
1874
|
a.textContent = part.name ?? part.uri;
|
|
1864
1875
|
a.title = part.uri;
|
|
@@ -1920,7 +1931,7 @@ function renderAiMessage(scrollEl, model) {
|
|
|
1920
1931
|
errEl.textContent = message;
|
|
1921
1932
|
},
|
|
1922
1933
|
stream() {
|
|
1923
|
-
const span = h("span", "ai-msg-stream-text");
|
|
1934
|
+
const span = h("span", "icen-ai-msg-text ai-msg-stream-text");
|
|
1924
1935
|
body.appendChild(span);
|
|
1925
1936
|
return createAiStream(span);
|
|
1926
1937
|
}
|
|
@@ -14,6 +14,7 @@ var ICEN_LOCALE_ZH_CN = {
|
|
|
14
14
|
"aiChat.roleTool": "\u5DE5",
|
|
15
15
|
"aiChat.roleUser": "\u6211",
|
|
16
16
|
"aiChat.thinking": "\u601D\u8003 {p0}",
|
|
17
|
+
"aiChat.thinkingNow": "\u6B63\u5728\u601D\u8003\u2026",
|
|
17
18
|
/* ── aiComposer ── */
|
|
18
19
|
"aiComposer.attachAgent": "\u4EE3\u7406",
|
|
19
20
|
"aiComposer.library": "\u6DFB\u52A0\u4E0A\u4E0B\u6587\u4E0E\u547D\u4EE4",
|
|
@@ -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",
|
|
@@ -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",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AiStatus, AiUsage } from './ai-core.
|
|
2
|
-
import { AiAuditor, AiAuditEntry } from './ai-provider.
|
|
1
|
+
import { AiStatus, AiUsage } from './ai-core.mjs';
|
|
2
|
+
import { AiAuditor, AiAuditEntry } from './ai-provider.mjs';
|
|
3
3
|
|
|
4
4
|
interface AiTodoItem {
|
|
5
5
|
content: 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",
|
|
@@ -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",
|
|
@@ -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",
|