@icen.ai/ui 0.9.0 → 0.10.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 +138 -80
- package/README.md +66 -91
- package/dist/base.css +208 -179
- package/dist/behaviors/accordion.mjs +8 -8
- package/dist/behaviors/ai-branch.d.mts +1 -1
- package/dist/behaviors/ai-branch.mjs +1010 -94
- package/dist/behaviors/ai-chat.d.mts +7 -7
- package/dist/behaviors/ai-chat.mjs +1071 -161
- package/dist/behaviors/ai-composer.d.mts +44 -12
- package/dist/behaviors/ai-composer.mjs +1997 -524
- package/dist/behaviors/ai-core.d.mts +1 -1
- package/dist/behaviors/ai-core.mjs +1016 -107
- package/dist/behaviors/ai-diff.mjs +1019 -127
- package/dist/behaviors/ai-feedback.d.mts +1 -1
- package/dist/behaviors/ai-feedback.mjs +1035 -126
- package/dist/behaviors/ai-panel.d.mts +8 -8
- package/dist/behaviors/ai-panel.mjs +1284 -266
- package/dist/behaviors/ai-provider.mjs +1003 -101
- package/dist/behaviors/ai-threads.d.mts +1 -1
- package/dist/behaviors/ai-threads.mjs +1068 -154
- package/dist/behaviors/ai-tool.d.mts +2 -2
- package/dist/behaviors/ai-tool.mjs +2037 -396
- package/dist/behaviors/ai-tools.d.mts +89 -16
- package/dist/behaviors/ai-tools.mjs +4448 -372
- package/dist/behaviors/autocomplete.d.mts +53 -0
- package/dist/behaviors/autocomplete.mjs +1600 -0
- package/dist/behaviors/back-top.d.mts +1 -1
- package/dist/behaviors/back-top.mjs +951 -14
- package/dist/behaviors/carousel.d.mts +1 -1
- package/dist/behaviors/carousel.mjs +906 -11
- package/dist/behaviors/cascader.d.mts +48 -0
- package/dist/behaviors/cascader.mjs +1853 -0
- package/dist/behaviors/charts.d.mts +72 -8
- package/dist/behaviors/charts.mjs +1901 -256
- package/dist/behaviors/code-block.d.mts +36 -0
- package/dist/behaviors/code-block.mjs +1611 -0
- package/dist/behaviors/command-palette.d.mts +9 -6
- package/dist/behaviors/command-palette.mjs +161 -59
- package/dist/behaviors/context-menu.d.mts +9 -8
- package/dist/behaviors/context-menu.mjs +1112 -44
- package/dist/behaviors/controls.d.mts +13 -13
- package/dist/behaviors/controls.mjs +989 -130
- package/dist/behaviors/copy.d.mts +21 -2
- package/dist/behaviors/copy.mjs +1206 -42
- package/dist/behaviors/datatable.d.mts +53 -3
- package/dist/behaviors/datatable.mjs +1963 -207
- package/dist/behaviors/date-picker.d.mts +0 -1
- package/dist/behaviors/date-picker.mjs +1340 -293
- package/dist/behaviors/design.d.mts +54 -0
- package/dist/behaviors/design.mjs +109 -0
- package/dist/behaviors/dropdown.d.mts +5 -6
- package/dist/behaviors/dropdown.mjs +262 -110
- package/dist/behaviors/events.d.mts +120 -3
- package/dist/behaviors/events.mjs +47 -2
- package/dist/behaviors/feedback.d.mts +30 -0
- package/dist/behaviors/feedback.mjs +296 -0
- package/dist/behaviors/focus.d.mts +8 -0
- package/dist/behaviors/focus.mjs +24 -0
- package/dist/behaviors/form-validate.d.mts +102 -0
- package/dist/behaviors/form-validate.mjs +6091 -0
- package/dist/behaviors/i18n.d.mts +789 -0
- package/dist/behaviors/i18n.mjs +1711 -0
- package/dist/behaviors/icons.d.mts +93 -0
- package/dist/behaviors/icons.mjs +113 -0
- package/dist/behaviors/input.d.mts +9 -1
- package/dist/behaviors/input.mjs +1056 -60
- package/dist/behaviors/kb-access.d.mts +1 -1
- package/dist/behaviors/kb-access.mjs +2129 -239
- package/dist/behaviors/kb-acl.d.mts +6 -6
- package/dist/behaviors/kb-acl.mjs +1119 -179
- package/dist/behaviors/kb-answer.mjs +2070 -418
- package/dist/behaviors/kb-audit.d.mts +1 -1
- package/dist/behaviors/kb-audit.mjs +1076 -154
- package/dist/behaviors/kb-canvas.mjs +1100 -177
- package/dist/behaviors/kb-chain.mjs +1022 -100
- package/dist/behaviors/kb-checkpoint.d.mts +1 -1
- package/dist/behaviors/kb-checkpoint.mjs +1319 -169
- package/dist/behaviors/kb-chunks.mjs +1152 -213
- package/dist/behaviors/kb-citation.d.mts +1 -1
- package/dist/behaviors/kb-citation.mjs +1201 -179
- package/dist/behaviors/kb-clarify.mjs +1058 -130
- package/dist/behaviors/kb-conflict.mjs +1041 -119
- package/dist/behaviors/kb-connector.mjs +1104 -176
- package/dist/behaviors/kb-core.d.mts +6 -1
- package/dist/behaviors/kb-core.mjs +1077 -162
- package/dist/behaviors/kb-eval.mjs +1971 -144
- package/dist/behaviors/kb-explain.d.mts +1 -1
- package/dist/behaviors/kb-explain.mjs +1024 -102
- package/dist/behaviors/kb-filter.mjs +2008 -178
- package/dist/behaviors/kb-gap.d.mts +1 -1
- package/dist/behaviors/kb-gap.mjs +1043 -121
- package/dist/behaviors/kb-hittest.d.mts +1 -1
- package/dist/behaviors/kb-hittest.mjs +1093 -150
- package/dist/behaviors/kb-metadata.mjs +1052 -130
- package/dist/behaviors/kb-passage.d.mts +2 -2
- package/dist/behaviors/kb-passage.mjs +1101 -161
- package/dist/behaviors/kb-pipeline.d.mts +2 -2
- package/dist/behaviors/kb-pipeline.mjs +1082 -162
- package/dist/behaviors/kb-qa.mjs +1088 -160
- package/dist/behaviors/kb-rerank.mjs +1047 -114
- package/dist/behaviors/kb-retrieval.mjs +2063 -224
- package/dist/behaviors/kb-review.mjs +1990 -159
- package/dist/behaviors/kb-sandbox.d.mts +1 -1
- package/dist/behaviors/kb-sandbox.mjs +1026 -104
- package/dist/behaviors/kb-segment.mjs +1111 -179
- package/dist/behaviors/kb-select.d.mts +24 -0
- package/dist/behaviors/kb-select.mjs +1989 -0
- package/dist/behaviors/kb-sources.d.mts +1 -1
- package/dist/behaviors/kb-sources.mjs +1084 -170
- package/dist/behaviors/kb-sql.d.mts +1 -1
- package/dist/behaviors/kb-sql.mjs +1143 -226
- package/dist/behaviors/kb-trace.d.mts +1 -1
- package/dist/behaviors/kb-trace.mjs +1042 -128
- package/dist/behaviors/kb-visibility.mjs +1086 -158
- package/dist/behaviors/kb-who-can.mjs +1974 -137
- package/dist/behaviors/mentions.d.mts +93 -0
- package/dist/behaviors/mentions.mjs +2152 -0
- package/dist/behaviors/modal.d.mts +4 -3
- package/dist/behaviors/modal.mjs +1055 -33
- package/dist/behaviors/nav.d.mts +1 -1
- package/dist/behaviors/nav.mjs +6 -6
- package/dist/behaviors/notification.d.mts +16 -8
- package/dist/behaviors/notification.mjs +1615 -265
- package/dist/behaviors/panel.d.mts +4 -0
- package/dist/behaviors/panel.mjs +994 -0
- package/dist/behaviors/popover.d.mts +28 -5
- package/dist/behaviors/popover.mjs +225 -24
- package/dist/behaviors/select.d.mts +5 -2
- package/dist/behaviors/select.mjs +1410 -117
- package/dist/behaviors/sidebar.d.mts +1 -1
- package/dist/behaviors/sidebar.mjs +13 -13
- package/dist/behaviors/slider.d.mts +1 -1
- package/dist/behaviors/slider.mjs +9 -9
- package/dist/behaviors/split-pane.d.mts +1 -1
- package/dist/behaviors/split-pane.mjs +7 -7
- package/dist/behaviors/suggestions.d.mts +27 -0
- package/dist/behaviors/suggestions.mjs +1089 -0
- package/dist/behaviors/svg-parse.d.mts +14 -0
- package/dist/behaviors/svg-parse.mjs +37 -0
- package/dist/behaviors/tabs.d.mts +2 -2
- package/dist/behaviors/tabs.mjs +25 -25
- package/dist/behaviors/tag-input.d.mts +23 -5
- package/dist/behaviors/tag-input.mjs +1359 -104
- package/dist/behaviors/theme-switcher.d.mts +24 -0
- package/dist/behaviors/theme-switcher.mjs +1981 -0
- package/dist/behaviors/theme.d.mts +15 -8
- package/dist/behaviors/theme.mjs +390 -28
- package/dist/behaviors/toast.d.mts +24 -25
- package/dist/behaviors/toast.mjs +1266 -88
- package/dist/behaviors/tooltip.d.mts +4 -0
- package/dist/behaviors/tooltip.mjs +378 -0
- package/dist/behaviors/tree-select.d.mts +69 -0
- package/dist/behaviors/tree-select.mjs +1907 -0
- package/dist/behaviors/tree.d.mts +1 -1
- package/dist/behaviors/tree.mjs +15 -15
- package/dist/behaviors/upload.d.mts +3 -2
- package/dist/behaviors/upload.mjs +1076 -66
- package/dist/cli.mjs +5 -6
- package/dist/components/ai-chat.css +666 -573
- package/dist/components/ai-diff.css +153 -132
- package/dist/components/ai-panel.css +351 -343
- package/dist/components/ai-tool.css +393 -238
- package/dist/components/autocomplete.css +61 -0
- package/dist/components/autocomplete.mjs +3 -0
- package/dist/components/back-top.css +34 -31
- package/dist/components/badge.css +44 -41
- package/dist/components/breadcrumb.css +67 -46
- package/dist/components/btn.css +80 -74
- package/dist/components/card.css +117 -106
- package/dist/components/cascader.css +311 -0
- package/dist/components/cascader.mjs +3 -0
- package/dist/components/chart-area.css +8 -2
- package/dist/components/chart-bar.css +95 -33
- package/dist/components/chart-gauge.css +14 -11
- package/dist/components/chart-graph.css +20 -17
- package/dist/components/chart-heatmap.css +96 -90
- package/dist/components/chart-line.css +8 -3
- package/dist/components/chart-pie.css +9 -6
- package/dist/components/chart-radar.css +8 -5
- package/dist/components/chart-scatter.css +9 -6
- package/dist/components/chart-sparkline.css +5 -2
- package/dist/components/chart-stack.css +14 -11
- package/dist/components/charts.css +164 -73
- package/dist/components/code-block.css +418 -0
- package/dist/components/code-block.mjs +4 -0
- package/dist/components/command-palette.css +138 -117
- package/dist/components/content.css +257 -251
- package/dist/components/copy.css +72 -44
- package/dist/components/datatable.css +423 -181
- package/dist/components/date-picker.css +135 -104
- package/dist/components/empty.css +68 -65
- package/dist/components/feedback.css +201 -183
- package/dist/components/form-validate.css +120 -0
- package/dist/components/form-validate.mjs +3 -0
- package/dist/components/form.css +150 -127
- package/dist/components/icon.css +102 -0
- package/dist/components/icon.mjs +3 -0
- package/dist/components/input.css +159 -104
- package/dist/components/kb-agent.css +188 -173
- package/dist/components/kb-data.css +257 -253
- package/dist/components/kb-ground.css +197 -185
- package/dist/components/kb-ingest.css +334 -350
- package/dist/components/kb-ops.css +265 -259
- package/dist/components/kb-perm.css +260 -271
- package/dist/components/kb-search.css +317 -360
- package/dist/components/kb.css +120 -109
- package/dist/components/layout.css +85 -82
- package/dist/components/material-chinese.css +73 -0
- package/dist/components/material-soviet.css +76 -0
- package/dist/components/material.css +275 -0
- package/dist/components/media.css +220 -166
- package/dist/components/mentions.css +189 -0
- package/dist/components/mentions.mjs +3 -0
- package/dist/components/menu.css +113 -105
- package/dist/components/modal.css +107 -112
- package/dist/components/nav.css +118 -115
- package/dist/components/notification.css +238 -174
- package/dist/components/pagination.css +56 -53
- package/dist/components/panel.css +66 -63
- package/dist/components/panel.mjs +1 -0
- package/dist/components/pill.css +81 -71
- package/dist/components/popover.css +67 -53
- package/dist/components/scroll-area.css +113 -110
- package/dist/components/segmented.css +146 -143
- package/dist/components/select.css +206 -186
- package/dist/components/sidebar.css +137 -127
- package/dist/components/slider.css +117 -114
- package/dist/components/split-pane.css +112 -109
- package/dist/components/stat.css +112 -106
- package/dist/components/steps.css +109 -103
- package/dist/components/switch.css +138 -134
- package/dist/components/table.css +131 -107
- package/dist/components/tabs.css +114 -108
- package/dist/components/tag-input.css +48 -133
- package/dist/components/tag.css +81 -71
- package/dist/components/theme-switcher.css +105 -0
- package/dist/components/theme-switcher.mjs +3 -0
- package/dist/components/toast.css +100 -86
- package/dist/components/toolbar.css +45 -42
- package/dist/components/tooltip.css +102 -43
- package/dist/components/tooltip.mjs +1 -0
- package/dist/components/tree-select.css +416 -0
- package/dist/components/tree-select.mjs +3 -0
- package/dist/components/upload.css +135 -92
- package/dist/fonts/LICENSES/ark-pixel/OFL.txt +96 -0
- package/dist/fonts/LICENSES/cubic-11/OFL.txt +108 -0
- package/dist/fonts/LICENSES/galmuri/LICENSE.txt +93 -0
- package/dist/fonts/OFL.txt +96 -0
- package/dist/fonts/README.md +14 -0
- package/dist/fonts/fusion-pixel-12px-zh-hans.woff2 +0 -0
- package/dist/index.d.mts +28 -14
- package/dist/index.mjs +20705 -9601
- package/dist/react.d.mts +91 -0
- package/dist/react.mjs +8208 -0
- package/dist/registry.json +130 -13
- package/dist/tokens/chinese.css +44 -0
- package/dist/tokens/colors.css +365 -1222
- package/dist/tokens/design.css +109 -0
- package/dist/tokens/dtcg.json +11198 -0
- package/dist/tokens/figma.variables.json +7017 -0
- package/dist/tokens/icon-masks.css +78 -0
- package/dist/tokens/pixel.css +50 -0
- package/dist/tokens/soviet.css +44 -0
- package/dist/tokens/style-profiles.css +36 -547
- package/dist/tokens/themes.json +308 -0
- package/dist/tokens/tokens-studio.json +6728 -0
- package/dist/tokens/typography.css +21 -18
- package/dist/tokens.css +1555 -1801
- package/dist/ui.css +11975 -9831
- package/dist/vue.d.mts +344 -0
- package/dist/vue.mjs +7781 -0
- package/docs/spec/ai-input-experience.md +75 -0
- package/docs/spec/ai-native.md +238 -147
- package/docs/spec/design-philosophy.md +189 -0
- package/docs/spec/design-primitives.md +196 -0
- package/docs/spec/kb-family.md +147 -115
- package/package.json +43 -8
- package/dist/retro-effects.css +0 -255
- package/dist/tokens/retro-effects.css +0 -255
- package/docs/research/2026-10-03-ai-kb-components.md +0 -392
- package/docs/research/2026-10-03-kb-permissions.md +0 -992
package/docs/spec/ai-native.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# AI 原生组件族 ·
|
|
1
|
+
# AI 原生组件族 · 设计规格
|
|
2
2
|
|
|
3
3
|
> 唯一事实源。施工代理按此文件执行,不得偏离契约(类名 / 导出签名 / 事件名)。
|
|
4
4
|
> 设计调研依据:AG-UI 协议、Vercel AI SDK UIMessage parts、OpenAI/Anthropic wire 格式、
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## 0. 设计原则
|
|
8
8
|
|
|
9
|
-
1. **继承用 CSS 变量级联实现**:`.ai-item` 基元定义 `--ai-item-*` 局部变量,kind/变体只覆盖变量不改结构(btn.css 的 `--btn-*` 模式的推广)。
|
|
9
|
+
1. **继承用 CSS 变量级联实现**:`.icen-ai-item` 基元定义 `--ai-item-*` 局部变量,kind/变体只覆盖变量不改结构(btn.css 的 `--btn-*` 模式的推广)。
|
|
10
10
|
2. **多态用 kind 注册表**:MCP、skill 不是独立组件,是 tool-call 的 kind。`registerAiKind()` 开放第三方扩展。
|
|
11
11
|
3. **组件只消费归一化模型**:业界格式(OpenAI / Anthropic / AI SDK / 素朴)由适配层统一翻译,组件不认识任何 provider。
|
|
12
12
|
4. **状态机是唯一语言**:所有 AI 条目共享 7 态,语义色与动画全库一致。
|
|
@@ -33,24 +33,24 @@ pending → running → streaming → done
|
|
|
33
33
|
|
|
34
34
|
协议映射:AI SDK `input-streaming/input-available`→pending、`approval-requested`→approval、`output-available`→done、`output-error`→error;AG-UI `TOOL_CALL_RESULT`→done、`outcome.interrupt`→approval;A2A `INPUT_REQUIRED/AUTH_REQUIRED`→approval、`WORKING`→running、终态→done/error/cancelled。
|
|
35
35
|
|
|
36
|
-
## 2. 条目基元 `.ai-item`(CSS 骨架,所有行式条目的"基类")
|
|
36
|
+
## 2. 条目基元 `.icen-ai-item`(CSS 骨架,所有行式条目的"基类")
|
|
37
37
|
|
|
38
38
|
```html
|
|
39
|
-
<div class="ai-item is-running" style="--ai-item-tint: var(--token-accent)">
|
|
40
|
-
<span class="ai-item-icon"><!-- svg --></span>
|
|
41
|
-
<div class="ai-item-main">
|
|
42
|
-
<div class="ai-item-title">一行摘要</div>
|
|
43
|
-
<div class="ai-item-sub">次要信息(路径/参数)</div>
|
|
39
|
+
<div class="icen-ai-item is-running" style="--ai-item-tint: var(--icen-token-accent)">
|
|
40
|
+
<span class="icen-ai-item-icon"><!-- svg --></span>
|
|
41
|
+
<div class="icen-ai-item-main">
|
|
42
|
+
<div class="icen-ai-item-title">一行摘要</div>
|
|
43
|
+
<div class="icen-ai-item-sub">次要信息(路径/参数)</div>
|
|
44
44
|
</div>
|
|
45
|
-
<div class="ai-item-side">
|
|
46
|
-
<span class="ai-item-status"></span> <!-- 状态点,纯 CSS 由 .is-* 驱动 -->
|
|
47
|
-
<span class="ai-item-meta">1.2s</span>
|
|
45
|
+
<div class="icen-ai-item-side">
|
|
46
|
+
<span class="icen-ai-item-status"></span> <!-- 状态点,纯 CSS 由 .is-* 驱动 -->
|
|
47
|
+
<span class="icen-ai-item-meta">1.2s</span>
|
|
48
48
|
</div>
|
|
49
|
-
<div class="ai-item-detail" hidden>展开详情</div>
|
|
49
|
+
<div class="icen-ai-item-detail" hidden>展开详情</div>
|
|
50
50
|
</div>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
局部变量:`--ai-item-tint`(图标/状态点着色槽,默认 accent)。状态点 `.ai-item-status` 是纯 CSS 圆点,由 `.is-*` 决定颜色与动效(running 脉冲用 `--pill-pulse-duration` 同款变量化时长)。
|
|
53
|
+
局部变量:`--ai-item-tint`(图标/状态点着色槽,默认 accent)。状态点 `.icen-ai-item-status` 是纯 CSS 圆点,由 `.is-*` 决定颜色与动效(running 脉冲用 `--pill-pulse-duration` 同款变量化时长)。
|
|
54
54
|
|
|
55
55
|
## 3. 文件与 slug 组织
|
|
56
56
|
|
|
@@ -87,20 +87,20 @@ pending → running → streaming → done
|
|
|
87
87
|
### 4.1 ai-chat(ai-chat.css + ai-chat.ts)
|
|
88
88
|
|
|
89
89
|
```html
|
|
90
|
-
<div class="ai-chat" data-density="normal"> <!-- verbose|normal|summary 透明度三档 -->
|
|
91
|
-
<div class="ai-chat-scroll">
|
|
92
|
-
<div class="ai-msg ai-msg--user | ai-msg--assistant | ai-msg--system | ai-msg--tool">
|
|
93
|
-
<span class="ai-msg-avatar"></span>
|
|
94
|
-
<div class="ai-msg-body">…内容(消费方可自行渲染 markdown)…</div>
|
|
95
|
-
<div class="ai-msg-actions"><button data-ai-msg-action="copy">…<button data-ai-msg-action="retry">…</div>
|
|
96
|
-
<div class="ai-msg-meta">12:04 · 1.2k tok</div>
|
|
90
|
+
<div class="icen-ai-chat" data-icen-density="normal"> <!-- verbose|normal|summary 透明度三档 -->
|
|
91
|
+
<div class="icen-ai-chat-scroll">
|
|
92
|
+
<div class="icen-ai-msg icen-ai-msg--user | icen-ai-msg--assistant | icen-ai-msg--system | icen-ai-msg--tool">
|
|
93
|
+
<span class="icen-ai-msg-avatar"></span>
|
|
94
|
+
<div class="icen-ai-msg-body">…内容(消费方可自行渲染 markdown)…</div>
|
|
95
|
+
<div class="icen-ai-msg-actions"><button data-icen-ai-msg-action="copy">…<button data-icen-ai-msg-action="retry">…</div>
|
|
96
|
+
<div class="icen-ai-msg-meta">12:04 · 1.2k tok</div>
|
|
97
97
|
</div>
|
|
98
98
|
</div>
|
|
99
|
-
<button class="ai-chat-jump" hidden>回到底部 · 3 条新消息</button>
|
|
99
|
+
<button class="icen-ai-chat-jump" hidden>回到底部 · 3 条新消息</button>
|
|
100
100
|
</div>
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
- `initAiChat(root?)`:幂等(`__icenAiChatInit`)。滚动钉底:scroll 监听,贴底时自动跟随新内容(监听 scroll 容器尺寸变化用 ResizeObserver);用户上滚暂停跟随并显示 `.ai-chat-jump`(带未读计数),点击回底恢复。消息动作委托:copy→复制正文+派 `icen:ai-copy`;retry→派 `icen:ai-retry`(detail 含消息元素)。reasoning 折叠委托也在这里(`.ai-reasoning-head` 点击切换)。
|
|
103
|
+
- `initAiChat(root?)`:幂等(`__icenAiChatInit`)。滚动钉底:scroll 监听,贴底时自动跟随新内容(监听 scroll 容器尺寸变化用 ResizeObserver);用户上滚暂停跟随并显示 `.icen-ai-chat-jump`(带未读计数),点击回底恢复。消息动作委托:copy→复制正文+派 `icen:ai-copy`;retry→派 `icen:ai-retry`(detail 含消息元素)。reasoning 折叠委托也在这里(`.icen-ai-reasoning-head` 点击切换)。
|
|
104
104
|
- `createAiStream(el)` → `{ append(text), done(), cancel(), fail(message?) }`:textContent 级追加(不解析 HTML),追加期间宿主挂 `.is-streaming`,done/cancel 移除;`fail()` 终止流并给宿主挂 `.is-error`(错误路径,append 之后失效)。Markdown 重渲染是消费方职责(文档里写明)。
|
|
105
105
|
- `renderAiMessage(scrollEl, model)` → `{ el, body, setMeta, setError, stream() }`:DOM API 构建整条消息(含多模态部件渲染,见 §10),返回句柄;`stream()` 在正文末尾开 `createAiStream`。model:
|
|
106
106
|
|
|
@@ -110,7 +110,7 @@ interface AiMessageModel {
|
|
|
110
110
|
content: AiContent; // string | AiContentPart[](§10)
|
|
111
111
|
meta?: string; // "12:04 · 1.2k tok"
|
|
112
112
|
model?: string; // assistant 消息的模型名标签
|
|
113
|
-
error?: string; // → .ai-msg--error + 错误文本行
|
|
113
|
+
error?: string; // → .icen-ai-msg--error + 错误文本行
|
|
114
114
|
streaming?: boolean; // 初始即流式态
|
|
115
115
|
}
|
|
116
116
|
```
|
|
@@ -118,11 +118,11 @@ interface AiMessageModel {
|
|
|
118
118
|
### 4.2 ai-message / ai-reasoning(ai-chat.css 一族)
|
|
119
119
|
|
|
120
120
|
```html
|
|
121
|
-
<div class="ai-reasoning is-done">
|
|
122
|
-
<button class="ai-reasoning-head" aria-expanded="false">
|
|
123
|
-
<span class="ai-reasoning-label">思考过程</span><span class="ai-reasoning-time">3s</span>
|
|
121
|
+
<div class="icen-ai-reasoning is-done">
|
|
122
|
+
<button class="icen-ai-reasoning-head" aria-expanded="false">
|
|
123
|
+
<span class="icen-ai-reasoning-label">思考过程</span><span class="icen-ai-reasoning-time">3s</span>
|
|
124
124
|
</button>
|
|
125
|
-
<div class="ai-reasoning-body" hidden>…</div>
|
|
125
|
+
<div class="icen-ai-reasoning-body" hidden>…</div>
|
|
126
126
|
</div>
|
|
127
127
|
```
|
|
128
128
|
|
|
@@ -131,40 +131,41 @@ interface AiMessageModel {
|
|
|
131
131
|
### 4.3 ai-composer(ai-chat.css + ai-composer.ts)
|
|
132
132
|
|
|
133
133
|
```html
|
|
134
|
-
<div class="ai-composer" data-ai-composer>
|
|
135
|
-
<div class="ai-composer-queue" hidden><!-- 排队消息 chips,可逐个 × --></div>
|
|
136
|
-
<div class="ai-composer-attach" hidden><!-- 附件 chips --></div>
|
|
137
|
-
<div class="ai-composer-box control">
|
|
138
|
-
<textarea class="ai-composer-input" rows="1" placeholder="…"></textarea>
|
|
139
|
-
<div class="ai-composer-
|
|
140
|
-
<
|
|
141
|
-
<button class="ai-composer-
|
|
134
|
+
<div class="icen-ai-composer" data-icen-ai-composer>
|
|
135
|
+
<div class="icen-ai-composer-queue" hidden><!-- 排队消息 chips,可逐个 × --></div>
|
|
136
|
+
<div class="icen-ai-composer-attach" hidden><!-- 附件 chips --></div>
|
|
137
|
+
<div class="icen-ai-composer-box icen-control">
|
|
138
|
+
<textarea class="icen-ai-composer-input" rows="1" placeholder="…"></textarea>
|
|
139
|
+
<div class="icen-ai-composer-toolbar">
|
|
140
|
+
<span class="icen-ai-composer-spacer"></span>
|
|
141
|
+
<button class="icen-ai-composer-btn" data-icen-ai-attach aria-label="附件"></button>
|
|
142
|
+
<button class="icen-ai-composer-send" data-icen-ai-send></button> <!-- 运行态变 .is-stop 停止钮 -->
|
|
142
143
|
</div>
|
|
143
144
|
</div>
|
|
144
145
|
</div>
|
|
145
146
|
```
|
|
146
147
|
|
|
147
|
-
- `initAiComposer(root?)`:autosize(上限 ~8 行后内滚);Enter 发送 / Shift+Enter 换行 / IME 组合态安全(复用 input.ts 模式);发送派 `icen:ai-send {text}` 并清空。
|
|
148
|
-
- **运行/停止态**:`setComposerRunning(el, bool)` 导出——运行中发送钮变停止钮(点击派 `icen:ai-stop`),此时输入不丢:回车转为入队(queue chip)并派 `icen:ai-queue {text}`;chip × 移除派 `icen:ai-dequeue {index}`。(queue / steer / stop 三动词分化,对齐 Cursor/Copilot 2026 模式)
|
|
148
|
+
- `initAiComposer(root?)`:autosize(上限 ~8 行后内滚);Enter 发送 / Shift+Enter 换行 / IME 组合态安全(复用 input.ts 模式);发送派 `icen:ai-send {text, refs?, files?}` 并清空。
|
|
149
|
+
- **运行/停止态**:`setComposerRunning(el, bool)` 导出——运行中发送钮变停止钮(点击派 `icen:ai-stop`),此时输入不丢:回车转为入队(queue chip)并派 `icen:ai-queue {text, refs?, files?}`;chip × 移除派 `icen:ai-dequeue {index}`。(queue / steer / stop 三动词分化,对齐 Cursor/Copilot 2026 模式)
|
|
149
150
|
- 附件钮打开 file input,选中文件生成 chip,派 `icen:ai-attach {files}`。
|
|
150
151
|
|
|
151
152
|
### 4.4 ai-tool-call(ai-tool.css + ai-tool.ts)
|
|
152
153
|
|
|
153
154
|
```html
|
|
154
|
-
<div class="ai-tool ai-tool--shell is-done"> <!-- kind 修饰类:--shell/--read/--edit/… -->
|
|
155
|
-
<button class="ai-tool-head" aria-expanded="false">
|
|
156
|
-
<span class="ai-item-icon"></span>
|
|
157
|
-
<span class="ai-item-main"><span class="ai-item-title">Read</span>
|
|
158
|
-
<span class="ai-item-sub">src/foo.ts:1-50</span></span>
|
|
159
|
-
<span class="ai-item-status"></span>
|
|
160
|
-
<span class="ai-item-meta">0.4s</span>
|
|
155
|
+
<div class="icen-ai-tool icen-ai-tool--shell is-done"> <!-- kind 修饰类:--shell/--read/--edit/… -->
|
|
156
|
+
<button class="icen-ai-tool-head" aria-expanded="false">
|
|
157
|
+
<span class="icen-ai-item-icon"></span>
|
|
158
|
+
<span class="icen-ai-item-main"><span class="icen-ai-item-title">Read</span>
|
|
159
|
+
<span class="icen-ai-item-sub">src/foo.ts:1-50</span></span>
|
|
160
|
+
<span class="icen-ai-item-status"></span>
|
|
161
|
+
<span class="icen-ai-item-meta">0.4s</span>
|
|
161
162
|
</button>
|
|
162
|
-
<div class="ai-tool-body" hidden>
|
|
163
|
-
<div class="ai-tool-io"><div class="ai-tool-io-label">输入</div><pre class="ai-tool-io-content">…</pre></div>
|
|
164
|
-
<div class="ai-tool-io"><div class="ai-tool-io-label">输出</div><pre class="ai-tool-io-content">…</pre></div>
|
|
165
|
-
<div class="ai-tool-approval"> <!-- 仅 .is-approval 显示 -->
|
|
166
|
-
<button class="btn btn-sm btn-primary" data-ai-approve>允许</button>
|
|
167
|
-
<button class="btn btn-sm" data-ai-reject>拒绝</button>
|
|
163
|
+
<div class="icen-ai-tool-body" hidden>
|
|
164
|
+
<div class="icen-ai-tool-io"><div class="icen-ai-tool-io-label">输入</div><pre class="icen-ai-tool-io-content">…</pre></div>
|
|
165
|
+
<div class="icen-ai-tool-io"><div class="icen-ai-tool-io-label">输出</div><pre class="icen-ai-tool-io-content">…</pre></div>
|
|
166
|
+
<div class="icen-ai-tool-approval"> <!-- 仅 .is-approval 显示 -->
|
|
167
|
+
<button class="icen-btn icen-btn-sm icen-btn-primary" data-icen-ai-approve>允许</button>
|
|
168
|
+
<button class="icen-btn icen-btn-sm" data-icen-ai-reject>拒绝</button>
|
|
168
169
|
</div>
|
|
169
170
|
</div>
|
|
170
171
|
</div>
|
|
@@ -179,17 +180,17 @@ interface AiMessageModel {
|
|
|
179
180
|
### 4.5 ai-subagent(ai-tool.css + ai-tool.ts)
|
|
180
181
|
|
|
181
182
|
```html
|
|
182
|
-
<div class="ai-subagent is-running">
|
|
183
|
-
<button class="ai-subagent-head" aria-expanded="false">
|
|
184
|
-
<span class="ai-item-icon"></span>
|
|
185
|
-
<span class="ai-item-main"><span class="ai-item-title">explore</span>
|
|
186
|
-
<span class="ai-item-sub">搜索 auth 模块的所有入口</span></span>
|
|
187
|
-
<span class="ai-item-status"></span>
|
|
188
|
-
<span class="ai-item-meta">12.3s · 4 步</span>
|
|
183
|
+
<div class="icen-ai-subagent is-running">
|
|
184
|
+
<button class="icen-ai-subagent-head" aria-expanded="false">
|
|
185
|
+
<span class="icen-ai-item-icon"></span>
|
|
186
|
+
<span class="icen-ai-item-main"><span class="icen-ai-item-title">explore</span>
|
|
187
|
+
<span class="icen-ai-item-sub">搜索 auth 模块的所有入口</span></span>
|
|
188
|
+
<span class="icen-ai-item-status"></span>
|
|
189
|
+
<span class="icen-ai-item-meta">12.3s · 4 步</span>
|
|
189
190
|
</button>
|
|
190
|
-
<div class="ai-subagent-body" hidden>
|
|
191
|
-
<div class="ai-subagent-stream"><!-- 嵌套 .ai-item / .ai-tool / .ai-reasoning 活动流 --></div>
|
|
192
|
-
<div class="ai-subagent-result">完成回执摘要</div>
|
|
191
|
+
<div class="icen-ai-subagent-body" hidden>
|
|
192
|
+
<div class="icen-ai-subagent-stream"><!-- 嵌套 .icen-ai-item / .icen-ai-tool / .icen-ai-reasoning 活动流 --></div>
|
|
193
|
+
<div class="icen-ai-subagent-result">完成回执摘要</div>
|
|
193
194
|
</div>
|
|
194
195
|
</div>
|
|
195
196
|
```
|
|
@@ -201,21 +202,21 @@ interface AiMessageModel {
|
|
|
201
202
|
### 4.6 ai-diff(ai-diff.css + ai-diff.ts)
|
|
202
203
|
|
|
203
204
|
```html
|
|
204
|
-
<div class="ai-diff">
|
|
205
|
-
<div class="ai-diff-file is-modified"> <!-- is-added/is-modified/is-deleted/is-renamed -->
|
|
206
|
-
<div class="ai-diff-head">
|
|
207
|
-
<span class="ai-diff-path">src/foo.ts</span>
|
|
208
|
-
<span class="ai-diff-stat"><b class="ai-diff-add">+12</b> <b class="ai-diff-del">−4</b></span>
|
|
209
|
-
<span class="ai-diff-actions">
|
|
210
|
-
<button data-ai-diff-accept>接受</button><button data-ai-diff-reject>拒绝</button>
|
|
205
|
+
<div class="icen-ai-diff">
|
|
206
|
+
<div class="icen-ai-diff-file is-modified"> <!-- is-added/is-modified/is-deleted/is-renamed -->
|
|
207
|
+
<div class="icen-ai-diff-head">
|
|
208
|
+
<span class="icen-ai-diff-path">src/foo.ts</span>
|
|
209
|
+
<span class="icen-ai-diff-stat"><b class="icen-ai-diff-add">+12</b> <b class="icen-ai-diff-del">−4</b></span>
|
|
210
|
+
<span class="icen-ai-diff-actions">
|
|
211
|
+
<button data-icen-ai-diff-accept>接受</button><button data-icen-ai-diff-reject>拒绝</button>
|
|
211
212
|
</span>
|
|
212
213
|
</div>
|
|
213
|
-
<div class="ai-diff-body" hidden>
|
|
214
|
-
<table class="ai-diff-table">
|
|
215
|
-
<tr class="ai-diff-line--hunk"><td colspan="3">@@ -10,6 +10,8 @@</td></tr>
|
|
216
|
-
<tr class="ai-diff-line--ctx"><td class="ai-diff-ln">10</td><td class="ai-diff-ln">10</td><td class="ai-diff-code">…</td></tr>
|
|
217
|
-
<tr class="ai-diff-line--add">…</tr>
|
|
218
|
-
<tr class="ai-diff-line--del">…</tr>
|
|
214
|
+
<div class="icen-ai-diff-body" hidden>
|
|
215
|
+
<table class="icen-ai-diff-table">
|
|
216
|
+
<tr class="icen-ai-diff-line--hunk"><td colspan="3">@@ -10,6 +10,8 @@</td></tr>
|
|
217
|
+
<tr class="icen-ai-diff-line--ctx"><td class="icen-ai-diff-ln">10</td><td class="icen-ai-diff-ln">10</td><td class="icen-ai-diff-code">…</td></tr>
|
|
218
|
+
<tr class="icen-ai-diff-line--add">…</tr>
|
|
219
|
+
<tr class="icen-ai-diff-line--del">…</tr>
|
|
219
220
|
</table>
|
|
220
221
|
</div>
|
|
221
222
|
</div>
|
|
@@ -229,8 +230,8 @@ interface AiMessageModel {
|
|
|
229
230
|
### 4.7 ai-files(ai-diff.css 一族,纯 CSS)
|
|
230
231
|
|
|
231
232
|
```html
|
|
232
|
-
<div class="ai-files">
|
|
233
|
-
<span class="ai-file-chip is-added"><span class="ai-file-icon"></span>src/foo.ts</span>
|
|
233
|
+
<div class="icen-ai-files">
|
|
234
|
+
<span class="icen-ai-file-chip is-added"><span class="icen-ai-file-icon"></span>src/foo.ts</span>
|
|
234
235
|
</div>
|
|
235
236
|
```
|
|
236
237
|
|
|
@@ -240,68 +241,68 @@ chip 态:`is-added/is-modified/is-deleted` + hover 操作(可选)。无 be
|
|
|
240
241
|
|
|
241
242
|
待办是**工具调用**(对话里只留折叠的 TodoWrite 卡,todo kind summarize 显示 x/y 完成),
|
|
242
243
|
**实时状态挂在输入区**而非对话流:`setComposerTodo(el, items | null)` 在工具条渲染
|
|
243
|
-
`.ai-composer-todo` chip(清单图标 + x/y;进行中 accent / 全部完成 success / 有失败
|
|
244
|
+
`.icen-ai-composer-todo` chip(清单图标 + x/y;进行中 accent / 全部完成 success / 有失败
|
|
244
245
|
warning),点击弹层展示完整清单(popover + renderAiTodo 只读);传 null 清除。
|
|
245
246
|
|
|
246
247
|
### 4.8 ai-todo(ai-panel.css + ai-panel.ts)
|
|
247
248
|
|
|
248
249
|
```html
|
|
249
|
-
<div class="ai-todo">
|
|
250
|
-
<div class="ai-todo-head"><span class="ai-todo-progress">2/5</span><div class="ai-todo-bar"><i style="width:40%"></i></div></div>
|
|
251
|
-
<div class="ai-todo-item is-running">
|
|
252
|
-
<span class="ai-item-status"></span>
|
|
253
|
-
<span class="ai-todo-text">实现登录页</span>
|
|
254
|
-
<span class="ai-todo-active">正在实现登录页…</span> <!-- activeForm:运行中替换文案(Claude Code 模式) -->
|
|
250
|
+
<div class="icen-ai-todo">
|
|
251
|
+
<div class="icen-ai-todo-head"><span class="icen-ai-todo-progress">2/5</span><div class="icen-ai-todo-bar"><i style="width:40%"></i></div></div>
|
|
252
|
+
<div class="icen-ai-todo-item is-running">
|
|
253
|
+
<span class="icen-ai-item-status"></span>
|
|
254
|
+
<span class="icen-ai-todo-text">实现登录页</span>
|
|
255
|
+
<span class="icen-ai-todo-active">正在实现登录页…</span> <!-- activeForm:运行中替换文案(Claude Code 模式) -->
|
|
255
256
|
</div>
|
|
256
257
|
</div>
|
|
257
258
|
```
|
|
258
259
|
|
|
259
|
-
- `renderAiTodo(el, items)`:`items: {content, status: AiStatus, activeForm?}[]`;`initAiTodo(root?)`:`data-ai-todo-interactive` 时可点击循环状态并派 `icen:ai-todo-toggle {index, status}`;默认只读。
|
|
260
|
+
- `renderAiTodo(el, items)`:`items: {content, status: AiStatus, activeForm?}[]`;`initAiTodo(root?)`:`data-icen-ai-todo-interactive` 时可点击循环状态并派 `icen:ai-todo-toggle {index, status}`;默认只读。
|
|
260
261
|
- 进度条用 feedback 的 progress 视觉(token 消费)。
|
|
261
262
|
|
|
262
263
|
### 4.9 ai-usage(ai-panel.css + ai-panel.ts)
|
|
263
264
|
|
|
264
265
|
```html
|
|
265
|
-
<div class="ai-usage">
|
|
266
|
-
<div class="ai-usage-bar">
|
|
267
|
-
<i class="ai-usage-seg ai-usage-seg--input" style="width:35%"></i>
|
|
268
|
-
<i class="ai-usage-seg ai-usage-seg--cache-read" style="width:20%"></i>
|
|
269
|
-
<i class="ai-usage-seg ai-usage-seg--cache-write" style="width:5%"></i>
|
|
270
|
-
<i class="ai-usage-seg ai-usage-seg--reasoning" style="width:10%"></i>
|
|
271
|
-
<i class="ai-usage-seg ai-usage-seg--output" style="width:12%"></i>
|
|
266
|
+
<div class="icen-ai-usage">
|
|
267
|
+
<div class="icen-ai-usage-bar">
|
|
268
|
+
<i class="icen-ai-usage-seg icen-ai-usage-seg--input" style="width:35%"></i>
|
|
269
|
+
<i class="icen-ai-usage-seg icen-ai-usage-seg--cache-read" style="width:20%"></i>
|
|
270
|
+
<i class="icen-ai-usage-seg icen-ai-usage-seg--cache-write" style="width:5%"></i>
|
|
271
|
+
<i class="icen-ai-usage-seg icen-ai-usage-seg--reasoning" style="width:10%"></i>
|
|
272
|
+
<i class="icen-ai-usage-seg icen-ai-usage-seg--output" style="width:12%"></i>
|
|
272
273
|
</div>
|
|
273
|
-
<div class="ai-usage-legend"><!-- 各段色点+数值;缓存分列(计费诚实) --></div>
|
|
274
|
-
<div class="ai-usage-total">82k / 200k · 41%</div>
|
|
274
|
+
<div class="icen-ai-usage-legend"><!-- 各段色点+数值;缓存分列(计费诚实) --></div>
|
|
275
|
+
<div class="icen-ai-usage-total">82k / 200k · 41%</div>
|
|
275
276
|
</div>
|
|
276
277
|
```
|
|
277
278
|
|
|
278
279
|
- `renderAiUsage(el, usage: AiUsage, opts?: { total?: number; cost?: number })`:分段条 + 图例 + 占比;`normalizeUsage` 在 ai-core。
|
|
279
280
|
- 配色:input=accent、output=success、cacheRead=info、cacheWrite=warning、reasoning=faint——语义 token。
|
|
280
|
-
- **环形形态 `renderAiUsageRing`**(Claude Desktop 式上下文窗口指示器):紧凑圆环(SVG dasharray,`viewBox 36 / r 15.9155` → 周长恰为 100),中心百分比;点击弹出完整分段分解(弹层为一次性动态 `.popover.ai-usage-popover`,复用 `openPopover`/`closePopover`,anchor 豁免外点关闭,Esc/外点/再点关闭即移除)。状态档:<60% is-ok(accent)/ 60–85% is-warn / >85% is-hot(error + 脉冲)。`--lg` 大号变体。
|
|
281
|
+
- **环形形态 `renderAiUsageRing`**(Claude Desktop 式上下文窗口指示器):紧凑圆环(SVG dasharray,`viewBox 36 / r 15.9155` → 周长恰为 100),中心百分比;点击弹出完整分段分解(弹层为一次性动态 `.icen-popover.icen-ai-usage-popover`,复用 `openPopover`/`closePopover`,anchor 豁免外点关闭,Esc/外点/再点关闭即移除)。状态档:<60% is-ok(icen-accent)/ 60–85% is-warn / >85% is-hot(error + 脉冲)。`--lg` 大号变体。
|
|
281
282
|
|
|
282
283
|
### 4.10 ai-context(ai-panel.css + ai-panel.ts)—— 右上角资源抽屉
|
|
283
284
|
|
|
284
285
|
```html
|
|
285
|
-
<aside class="ai-context" hidden>
|
|
286
|
-
<div class="ai-context-head"><span>上下文</span><button data-ai-context-close>×</button></div>
|
|
287
|
-
<section class="ai-context-section"><h3>用量</h3><!-- ai-usage --></section>
|
|
288
|
-
<section class="ai-context-section"><h3>文件 (3)</h3><!-- ai-files chips --></section>
|
|
289
|
-
<section class="ai-context-section"><h3>MCP (2)</h3><!-- server 行:名称+状态点+工具数 --></section>
|
|
290
|
-
<section class="ai-context-section"><h3>Skills</h3><!-- skill chip 列表 --></section>
|
|
286
|
+
<aside class="icen-ai-context" hidden>
|
|
287
|
+
<div class="icen-ai-context-head"><span>上下文</span><button data-icen-ai-context-close>×</button></div>
|
|
288
|
+
<section class="icen-ai-context-section"><h3>用量</h3><!-- ai-usage --></section>
|
|
289
|
+
<section class="icen-ai-context-section"><h3>文件 (3)</h3><!-- ai-files chips --></section>
|
|
290
|
+
<section class="icen-ai-context-section"><h3>MCP (2)</h3><!-- server 行:名称+状态点+工具数 --></section>
|
|
291
|
+
<section class="icen-ai-context-section"><h3>Skills</h3><!-- skill chip 列表 --></section>
|
|
291
292
|
</aside>
|
|
292
293
|
```
|
|
293
294
|
|
|
294
|
-
- `initAiContext(root?)`:触发器 `[data-ai-context-open]` 全局委托开合;抽屉 fixed 右侧滑入(`--z-chrome`),Esc/外点关闭;`.ai-context--inline` 变体为页面流内嵌(不做 fixed)。开/合从抽屉根派 `icen:ai-context-open` / `icen:ai-context-close` `{}`。
|
|
295
|
+
- `initAiContext(root?)`:触发器 `[data-icen-ai-context-open]` 全局委托开合;抽屉 fixed 右侧滑入(`--icen-z-chrome`),Esc/外点关闭;`.icen-ai-context--inline` 变体为页面流内嵌(不做 fixed)。开/合从抽屉根派 `icen:ai-context-open` / `icen:ai-context-close` `{}`。
|
|
295
296
|
- `renderAiContext(el, { usage?, files?, mcpServers?, skills? })`:组合渲染(内部复用 renderAiUsage / ai-files chip / ai-item 行)。
|
|
296
|
-
- MCP server 行:`.ai-item` 基元 + `is-done`(已连接)/`is-error`(断开)。
|
|
297
|
+
- MCP server 行:`.icen-ai-item` 基元 + `is-done`(已连接)/`is-error`(断开)。
|
|
297
298
|
|
|
298
299
|
## 5. 事件汇总(全部 bubbles)
|
|
299
300
|
|
|
300
301
|
| 事件 | detail | 来源 |
|
|
301
302
|
|---|---|---|
|
|
302
|
-
| `icen:ai-send` | `{text}` | composer
|
|
303
|
+
| `icen:ai-send` | `{text, refs?, files?}` | composer 发送快照 |
|
|
303
304
|
| `icen:ai-stop` | `{}` | composer 停止 |
|
|
304
|
-
| `icen:ai-queue` / `icen:ai-dequeue` | `{text}` / `{index}` |
|
|
305
|
+
| `icen:ai-queue` / `icen:ai-dequeue` | `{text, refs?, files?}` / `{index}` | 排队消息快照 |
|
|
305
306
|
| `icen:ai-attach` | `{files}` | 附件(钮选 / 粘贴 / 拖放共用) |
|
|
306
307
|
| `icen:ai-copy` / `icen:ai-retry` | `{el}` | 消息操作 |
|
|
307
308
|
| `icen:ai-approve` / `icen:ai-reject` | `{id, kind}` | 工具审批 |
|
|
@@ -320,7 +321,7 @@ warning),点击弹层展示完整清单(popover + renderAiTodo 只读)
|
|
|
320
321
|
```ts
|
|
321
322
|
export type AiStatus = 'pending'|'running'|'streaming'|'approval'|'done'|'error'|'cancelled';
|
|
322
323
|
export interface AiUsage { input?; output?; cacheRead?; cacheWrite?; reasoning?; total?; }
|
|
323
|
-
export interface AiKindDef { label: string; icon: string; tint?: 'accent'|'success'|'warning'|'error'|'info'|'muted'; summarize?: (input: unknown) => string; }
|
|
324
|
+
export interface AiKindDef { label: string; icon: string; tint?: 'icen-accent'|'success'|'warning'|'error'|'info'|'muted'; summarize?: (input: unknown) => string; }
|
|
324
325
|
export interface AiToolCallModel {
|
|
325
326
|
id: string; name: string; kind: string; status: AiStatus;
|
|
326
327
|
input?: unknown; output?: unknown; errorText?: string;
|
|
@@ -356,18 +357,18 @@ export function svgIcon(svg: string): SVGElement | null; // DOMParser 消毒(
|
|
|
356
357
|
结构(自底向上工具条,业界主流形态):
|
|
357
358
|
|
|
358
359
|
```html
|
|
359
|
-
<div class="ai-composer" data-ai-composer>
|
|
360
|
-
<div class="ai-composer-queue" hidden></div> <!-- 排队 chips(v1 已有) -->
|
|
361
|
-
<div class="ai-composer-refs" hidden></div> <!-- @ 引用 chips(分离渲染:textarea 不嵌 chip) -->
|
|
362
|
-
<div class="ai-composer-attach" hidden></div> <!-- 附件 chips(v1 已有) -->
|
|
363
|
-
<div class="ai-composer-box control">
|
|
364
|
-
<textarea class="ai-composer-input" rows="1"></textarea>
|
|
365
|
-
<div class="ai-composer-toolbar">
|
|
366
|
-
<button class="ai-composer-model" data-ai-model-open> <!-- 模型切换:provider 图标 + 模型名 + chevron -->
|
|
367
|
-
<span class="ai-composer-spacer"></span>
|
|
368
|
-
<span class="ai-composer-usage" data-ai-usage></span> <!-- 上下文环挂载点 -->
|
|
369
|
-
<button class="ai-composer-btn" data-ai-attach></button>
|
|
370
|
-
<button class="ai-composer-send" data-ai-send></button>
|
|
360
|
+
<div class="icen-ai-composer" data-icen-ai-composer>
|
|
361
|
+
<div class="icen-ai-composer-queue" hidden></div> <!-- 排队 chips(v1 已有) -->
|
|
362
|
+
<div class="icen-ai-composer-refs" hidden></div> <!-- @ 引用 chips(分离渲染:textarea 不嵌 chip) -->
|
|
363
|
+
<div class="icen-ai-composer-attach" hidden></div> <!-- 附件 chips(v1 已有) -->
|
|
364
|
+
<div class="icen-ai-composer-box icen-control">
|
|
365
|
+
<textarea class="icen-ai-composer-input" rows="1"></textarea>
|
|
366
|
+
<div class="icen-ai-composer-toolbar">
|
|
367
|
+
<button class="icen-ai-composer-model" data-icen-ai-model-open> <!-- 模型切换:provider 图标 + 模型名 + chevron -->
|
|
368
|
+
<span class="icen-ai-composer-spacer"></span>
|
|
369
|
+
<span class="icen-ai-composer-usage" data-icen-ai-usage></span> <!-- 上下文环挂载点 -->
|
|
370
|
+
<button class="icen-ai-composer-btn" data-icen-ai-attach></button>
|
|
371
|
+
<button class="icen-ai-composer-send" data-icen-ai-send></button>
|
|
371
372
|
</div>
|
|
372
373
|
</div>
|
|
373
374
|
</div>
|
|
@@ -377,15 +378,15 @@ export function svgIcon(svg: string): SVGElement | null; // DOMParser 消毒(
|
|
|
377
378
|
|
|
378
379
|
- `setComposerModels(el, providers: AiProviderOption[], current?: {provider, model})`:
|
|
379
380
|
`AiProviderOption = { id, label, icon?, models: { id, label, context? }[] }`。
|
|
380
|
-
工具条出现模型钮;点击开 `.ai-composer-popup`(portal body,`computePopoverLayout` side:top)——
|
|
381
|
-
按 provider 分组(图标+名)+ 模型行(label + 上下文窗口如 `1M`)+ 搜索过滤 +
|
|
381
|
+
工具条出现模型钮;点击开 `.icen-ai-composer-popup`(portal body,`computePopoverLayout` side:top)——
|
|
382
|
+
按 provider 分组(图标+名)+ 模型行(label + 上下文窗口如 `1M`)+ 搜索过滤 + 当前轻高亮与内描边。
|
|
382
383
|
选择派 `icen:ai-model-change { provider, model, label, context }`;
|
|
383
384
|
**切换时若 cacheRead 占比高则弹层内提示"换模型会使 prompt 缓存失效"**(Kimi Code 细节)。
|
|
384
385
|
选中模型的 `context` 自动作为 `setComposerUsage` 的 total 默认值(闭环)。
|
|
385
386
|
- `setComposerCommands(el, commands: { name, description?, argsHint? }[])`:
|
|
386
387
|
输入开头为 `/` 时弹层过滤;↑↓ 导航、Enter/Tab 选中、Esc 关;选中派 `icen:ai-command { name, args }`
|
|
387
388
|
并清空输入(命令被拦截执行、不进消息流——业界共识)。
|
|
388
|
-
- `setComposerRefs(el, sources: { kind: 'file'|'folder'|'doc'|'agent', id, label, sub? }[])`:
|
|
389
|
+
- `setComposerRefs(el, sources: { kind: 'skill'|'tool'|'file'|'folder'|'doc'|'agent' | string, id, label, sub? }[])`:
|
|
389
390
|
任意位置输入 `@` 触发弹层(分组按 kind + 图标);选中后文本插入 `@label ` 且 refs chip 行 +1,
|
|
390
391
|
派 `icen:ai-ref { action: 'add', ref }`;chip × 移除(同时删文本里首个 `@label`)派 `{ action: 'remove' }`。
|
|
391
392
|
kind 开放注册:`registerRefKind(kind, { label, icon?, order? })`(照 registerAiKind 的 Map + 兜底模式);
|
|
@@ -393,8 +394,8 @@ export function svgIcon(svg: string): SVGElement | null; // DOMParser 消毒(
|
|
|
393
394
|
- `setComposerUsage(el, usage, opts)`:工具条右侧挂 `renderAiUsageRing`。
|
|
394
395
|
- 历史:输入为空时按 `↑` 取回上一条已发送文本(会话内历史数组,每 composer 独立)。
|
|
395
396
|
- 运行态(v1 已有 queue/stop)补充:运行中按 `↑` 把最后一条排队消息取回输入框重新编辑(Claude Code 模式)。
|
|
396
|
-
-
|
|
397
|
-
-
|
|
397
|
+
- 弹层接管方向与确认键,目录/模型搜索保留正常 Tab;IME 组合态安全。
|
|
398
|
+
- 四弹层尺寸走 PanelSizing 契约:composer 根 `data-panel-*` 为用户覆盖(四弹层共用),
|
|
398
399
|
`bindComposer` opts.popoverSizing 整体覆盖,原内置魔数仅为默认。
|
|
399
400
|
- 图标对齐修复:工具条按钮统一 28px 方形、svg 14px 居中、gap 走 density token。
|
|
400
401
|
|
|
@@ -491,7 +492,7 @@ client 每次请求自动产审计记录(onAudit 回调 + 若传了 auditor
|
|
|
491
492
|
|
|
492
493
|
---
|
|
493
494
|
|
|
494
|
-
## 10. 标准化内容模型 `AiContent`(
|
|
495
|
+
## 10. 标准化内容模型 `AiContent`(ai-core)
|
|
495
496
|
|
|
496
497
|
> 调研依据(2026-10):Vercel AI SDK v5 UIMessage parts(text / reasoning / tool-\<name\> 六态 / file{mediaType,url} / source-url / source-document / data-*)、
|
|
497
498
|
> MCP 内容类型(TextContent / ImageContent / AudioContent / ResourceLink / EmbeddedResource + structuredContent,全部带 annotations)、
|
|
@@ -553,7 +554,7 @@ export function contextEstimate(usage: AiUsage): number;
|
|
|
553
554
|
|
|
554
555
|
---
|
|
555
556
|
|
|
556
|
-
## 11. 绑定层 `bindComposer`(
|
|
557
|
+
## 11. 绑定层 `bindComposer`(ai-composer.ts)
|
|
557
558
|
|
|
558
559
|
「零接线全链路」的胶水:**事件驱动、消费方拥有传输会话的所有权不变**。绑定层监听 §5 的 `icen:ai-*` 事件把 composer ↔ 消息区 ↔ client ↔ 用量环接成闭环。
|
|
559
560
|
|
|
@@ -562,25 +563,27 @@ export interface AiComposerBindOpts {
|
|
|
562
563
|
client?: AiClient; // 传入即托管对话:send → 用户消息渲染 + running + client.stream
|
|
563
564
|
// → assistant 消息 + createAiStream 逐 chunk 追加 → done 收尾
|
|
564
565
|
// (错误 → fail + setError;stop → session.cancel;排队消息在
|
|
565
|
-
//
|
|
566
|
-
messages?: HTMLElement; // .ai-chat-scroll 或任意消息容器:renderAiMessage 的挂载点(client 模式必填)
|
|
566
|
+
// 成功后自动续发,停止/失败则暂停队列)
|
|
567
|
+
messages?: HTMLElement; // .icen-ai-chat-scroll 或任意消息容器:renderAiMessage 的挂载点(client 模式必填)
|
|
567
568
|
usage?: { from: 'context' | 'billing' | AiAuditor; total?: number };
|
|
568
569
|
// 环口径:'context'(默认,icen:ai-done 的 contextEstimate——环语义正确)
|
|
569
570
|
// | 'billing'(icen:ai-done 的请求 usage 累加——计费视角)
|
|
570
571
|
// | 传 auditor 实例则用其 summary()
|
|
571
572
|
running?: boolean; // 运行态自动管理(默认 true):icen:ai-send → running;icen:ai-done → 解除
|
|
572
573
|
}
|
|
573
|
-
export interface AiComposerBinding { unbind(): void }
|
|
574
|
+
export interface AiComposerBinding { retry(): boolean; unbind(): void }
|
|
574
575
|
export function bindComposer(el: HTMLElement, opts?: AiComposerBindOpts): AiComposerBinding;
|
|
575
576
|
```
|
|
576
577
|
|
|
577
578
|
- **client 模式**下 stop 钮绑定 `session.cancel()`;`icen:ai-done` 的 `cost` 自动进环的 `opts.cost` 累计;assistant 消息 meta 自动填 token 数与模型名。
|
|
579
|
+
- 停止立即结束本轮 UI,保留部分输出并暂停排队;空输入框按 ↑ 可取回待发消息。停止/解绑后迟到的 chunk 与 done 不能覆写状态或继续发送队列。
|
|
580
|
+
- 最近失败或停止的回答可通过 `binding.retry()` 或消息的继续生成/重试事件原位重试;保留消息节点与当前输入草稿,不重复添加用户消息。忙碌、已解绑或没有可重试回答时返回 false。更早消息和成功回答的重试仍由宿主处理。
|
|
578
581
|
- 不传 client 时是纯状态绑定(send→running、done→解除、环更新),传输仍归消费方——**渐进采用**。
|
|
579
582
|
- composer 同时新增:粘贴文件与拖放文件落附件(`icen:ai-attach` 同事件);`MODEL_CTX` 在选中模型无 `context` 时显式清除(环回退无上限形态)。
|
|
580
583
|
|
|
581
584
|
---
|
|
582
585
|
|
|
583
|
-
## 12. 定价与审计闭环(
|
|
586
|
+
## 12. 定价与审计闭环(ai-provider.ts + ai-panel.ts)
|
|
584
587
|
|
|
585
588
|
### 12.1 定价表(注册表内联,USD / MTok,2026-10 调研近似值)
|
|
586
589
|
|
|
@@ -609,54 +612,142 @@ GLM-5.x ~$0.5/$3 档;Kimi K3 $3/$15、缓存读 ~$0.3,K2.6 ~$0.95/$3)。ca
|
|
|
609
612
|
|
|
610
613
|
---
|
|
611
614
|
|
|
612
|
-
## 13. AI
|
|
615
|
+
## 13. AI 工具体系(`src/behaviors/ai-tools.ts`)
|
|
613
616
|
|
|
614
617
|
> 任何 UI 能力(图表渲染、未来的 diff 应用 / todo 写入……)注册为 **AiToolDef**,
|
|
615
618
|
> 模型侧可调用、宿主侧全控。库内置 `render_chart` 一个工具(吃 ChartSpec 纯 JSON)。
|
|
619
|
+
> 设计纪律:**诚实失败优先于智能猜测**——参数校验不支持的关键字记清单不静默,
|
|
620
|
+
> 超时/拒绝/失败一律落 `record.icen-result = { ok:false, error:{ code, … } }` 信封。
|
|
616
621
|
|
|
617
|
-
### 13.1
|
|
622
|
+
### 13.1 注册表(AiToolDef 执行契约)
|
|
618
623
|
|
|
619
624
|
```ts
|
|
625
|
+
export interface AiToolRunContext {
|
|
626
|
+
signal: AbortSignal; // 超时、卡片移除/淘汰/清空、unregister 时 abort
|
|
627
|
+
progress(line: string): void; // 活动流:追加进挂载卡 .icen-ai-tools-item-stream
|
|
628
|
+
}
|
|
629
|
+
|
|
620
630
|
export interface AiToolDef<TInput = unknown> {
|
|
621
631
|
name: string; // 模型侧 function name(snake_case)
|
|
622
|
-
description: string; //
|
|
632
|
+
description: string; // 给模型看的说明;requiresApproval 时兼作审批区原因行
|
|
623
633
|
inputSchema: Record<string, unknown>; // OpenAI parameters / MCP inputSchema 兼容
|
|
624
|
-
run: (input: TInput, mount: HTMLElement) => unknown;
|
|
634
|
+
run: (input: TInput, mount: HTMLElement, ctx: AiToolRunContext) => unknown;
|
|
625
635
|
present?: 'mount' | 'data';
|
|
636
|
+
timeoutMs?: number; // 超时 → { ok:false, error:{ code:'timeout' } }(只作用于 Promise 型 run)
|
|
637
|
+
requiresApproval?: boolean; // 执行前审批门禁(仅 'mount' 模式生效;'data' 无卡可审批)
|
|
638
|
+
idempotent?: boolean; // 同名同输入在途调用复用同一条记录
|
|
626
639
|
}
|
|
627
640
|
registerAiTool(def) / unregisterAiTool(name) / getAiTool(name) / listAiTools()
|
|
628
641
|
```
|
|
629
642
|
|
|
630
|
-
|
|
643
|
+
- **Promise 结果契约**:`run` 允许返回 Promise;area 在落定后写 `record.icen-result` 并补派
|
|
644
|
+
`icen:ai-tool-result`(落定前不派)。超时竞速后晚到的兑现/拒绝被丢弃并吞掉
|
|
645
|
+
(不冒 unhandledrejection)。
|
|
646
|
+
- **timeoutMs 诚实边界**:只作用于 Promise 型 run——同步 run 在单线程内无法中断,
|
|
647
|
+
设了也不生效(不假装)。定时器触发 → `signal.abort()` + 结果
|
|
648
|
+
`{ ok:false, error:{ code:'timeout', timeoutMs } }`,卡片 `.is-error`。
|
|
649
|
+
- **失败统一信封**:校验失败 `code:'validation'`(含 errors/unvalidated 明细)、
|
|
650
|
+
执行异常 `code:'exec'`(含 message)、超时 `code:'timeout'`、审批拒绝 `code:'rejected'`。
|
|
651
|
+
事件 `icen:ai-tool-result` 的 `error` 字段按 events.ts 契约为 **string** 摘要,
|
|
652
|
+
机器可读细节一律在 `record.icen-result`。
|
|
653
|
+
|
|
654
|
+
### 13.2 参数校验 `validateToolArgs`(诚实纪律)
|
|
655
|
+
|
|
656
|
+
```ts
|
|
657
|
+
validateToolArgs(schema, args) → { ok, errors: { path, message }[], unvalidated: string[] }
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
- 支持关键字:`type`(object/array/string/number/integer/boolean/null)/ `required` /
|
|
661
|
+
`properties`(递归不限深度)/ `enum` / `items`(**只展开一层**:二层数组记
|
|
662
|
+
`unvalidated` 不展开)/ `minimum` / `maximum` / `minLength` / `maxLength`。
|
|
663
|
+
- **不支持的关键字不猜不静默**:`oneOf`/`anyOf`/`allOf`/`not`/`$ref`/`format`/`pattern`/
|
|
664
|
+
`additionalProperties`/`minItems`/`uniqueItems` 等出现在**实际访问到的节点**上时,
|
|
665
|
+
以 schema 路径(`'$.properties.email.format'`、`'$.properties.matrix.items.items'`、
|
|
666
|
+
根节点直接 `'$.additionalProperties'`)记入 `unvalidated`,
|
|
667
|
+
该断言不生效也不报错。注解类关键字(description/title/default/examples…)不作出
|
|
668
|
+
校验承诺,静默跳过、不进清单。未访问节点的关键字不进清单(清单只对实际校验负责)。
|
|
669
|
+
- 错误 `path` 为 JSON-pointer 风参数路径(`'$'` 根;`'$.tags[1]'`、`'$.series[2].name'`);
|
|
670
|
+
多错误并列收集不短路。`args` 为 `undefined` 的可选字段不做任何断言(由 required 把关)。
|
|
671
|
+
- `parseAiToolArgs(raw)` 保持现状:模型回包容错解析(JSON 字符串/对象/坏 JSON → `{}),
|
|
672
|
+
不做校验——校验只走 `validateToolArgs`。
|
|
673
|
+
|
|
674
|
+
### 13.3 挂载区(挂不挂 / 挂哪些 / 挂几个——宿主三层控制)
|
|
631
675
|
|
|
632
676
|
```ts
|
|
633
677
|
createAiToolArea(el, { tools?: string[]; max?: number; itemMinHeight?: number }) → AiToolArea
|
|
634
|
-
// tools 白名单支持通配 'chart-*';max 超出
|
|
635
|
-
// AiToolArea: { el, count, call(name, input), can(name), setTools, setMax, clear, onChange(fn) }
|
|
678
|
+
// tools 白名单支持通配 'chart-*';max 超出 LRU 淘汰最旧挂载(present:'data' 的工具不占挂载位、不参与淘汰)
|
|
679
|
+
// AiToolArea: { el, count, call(name, input, opts?), can(name), setTools, setMax, clear, onChange(fn) }
|
|
680
|
+
call(name, input, opts?: { validate?: boolean }) // validate 默认 true:校验失败不执行 run
|
|
636
681
|
```
|
|
637
682
|
|
|
638
683
|
- 三层控制:① 不 import 本模块 = 生态无工具体系(tree-shake);② area 白名单 + max;
|
|
639
|
-
③ 运行时 setTools/setMax/clear + unregisterAiTool
|
|
640
|
-
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
684
|
+
③ 运行时 setTools/setMax/clear + unregisterAiTool 全局下架(联动摘除各区域已挂载卡)。
|
|
685
|
+
- **call 管线**:白名单/注册检查 → `parseAiToolArgs` 归一 → 校验(可关)→ 幂等复用 →
|
|
686
|
+
挂载卡('mount')/ 纯记录('data')→ 审批门禁(requiresApproval)→ 执行。
|
|
687
|
+
每次 call 在挂载时触发一次 `onChange`,result 落定时再触发一次。
|
|
688
|
+
- 挂载卡结构(状态类挂 `.icen-ai-tools-item`,7 态状态机的挂载区形态):
|
|
689
|
+
|
|
690
|
+
```
|
|
691
|
+
.icen-ai-tools-mount > .icen-ai-tools-item(.is-approval/.is-running/.is-done/.is-error/.is-cancelled)
|
|
692
|
+
> head(折叠钮 chevron/工具名/时间/状态点 .icen-ai-tools-item-status/×,button,aria-expanded)
|
|
693
|
+
+ .icen-ai-tools-item-approval(仅审批态:原因行 = description + 归一输入 JSON 预览 + 批准/拒绝钮,裁决后移除)
|
|
694
|
+
+ .icen-ai-tools-item-error(仅失败态:逐行一条错误,role=alert)
|
|
695
|
+
+ .icen-ai-tools-item-body(工具自渲染,默认展开,head 点击折叠;折叠同时隐藏活动流)
|
|
696
|
+
+ .icen-ai-tools-item-stream(活动流,progress(line) 懒建追加)
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
- 校验失败仍挂载错误态卡(逐条列出 errors)并成对派 call + result(审计可关联),不执行 run。
|
|
700
|
+
- 事件(bubbles,全部经 emitIcen):`icen:ai-tool-call {name, input, el}`(挂载即派,input 为原始入参)/
|
|
701
|
+
`icen:ai-tool-result {name, ok, el, error?, count}`(error 为 string 摘要)/
|
|
702
|
+
`icen:ai-tool-evict {name, el}`。审批两事件见 §13.4。
|
|
703
|
+
- `ctx.signal` 中止时机:超时触发、卡片被 × 移除/LRU 淘汰/clear/unregister 下架。
|
|
704
|
+
|
|
705
|
+
### 13.4 审批流与幂等(requiresApproval / idempotent)
|
|
706
|
+
|
|
707
|
+
- **requiresApproval**:call 时挂载卡进入 `.is-approval`(warning 边框 + 状态点外环),
|
|
708
|
+
审批区 = 原因行(description)+ 输入 JSON 预览 + 批准/拒绝按钮。**批准** → 派
|
|
709
|
+
`icen:ai-tool-approve {name, input, el}`(input 为归一后的 Record)→ 移除审批区 →
|
|
710
|
+
执行 run;**拒绝** → 派 `icen:ai-tool-reject {name, input, el}` → 卡片 `.is-cancelled`
|
|
711
|
+
(标题删除线),结果 `{ ok:false, error:{ code:'rejected' } }`,result 事件 `ok:false`。
|
|
712
|
+
'data' 模式无挂载卡,requiresApproval 不生效(文档注明,不静默假装有门禁)。
|
|
713
|
+
- **idempotent**:以 `name + JSON.stringify(parseAiToolArgs(input))` 为键——JSON 字符串与
|
|
714
|
+
对象形态的同输入同键;在途(执行中/待审批)的同键调用返回**同一条 record 引用**
|
|
715
|
+
(不重复挂载、不重复执行);落定后同键调用正常重新执行。
|
|
716
|
+
|
|
717
|
+
### 13.5 模型侧导出(agent loop 直接消费;四族共享同一归一管线)
|
|
645
718
|
|
|
646
719
|
```ts
|
|
647
|
-
aiToolsToOpenAI(names?)
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
720
|
+
aiToolsToOpenAI(names?) // → [{ type:'function', function:{ name, description, parameters } }]
|
|
721
|
+
aiToolsToAnthropic(names?) // → [{ name, description, input_schema }]
|
|
722
|
+
aiToolsToMcp(names?) // → [{ name, description, inputSchema }]
|
|
723
|
+
aiToolsToAiSdk(names?) // → Record<name, { description, inputSchema }>——Vercel AI SDK v5 tool() 描述表
|
|
724
|
+
aiToolsManifest(names?) // → system prompt 能力清单文本
|
|
725
|
+
parseAiToolArgs(raw) // tool_call 参数容错解析(JSON 字符串/对象/坏 JSON→{})
|
|
651
726
|
```
|
|
652
727
|
|
|
653
|
-
|
|
728
|
+
- 四族经私有 `toDescriptor` 归一后各自映射(names 缺省 = 全部,支持通配过滤),
|
|
729
|
+
name/description/inputSchema 内容严格一致;`aiToolsToAiSdk` **不携带 execute**——
|
|
730
|
+
执行永远留在宿主侧(createAiToolArea 挂载)。
|
|
731
|
+
- 宿主 agent loop 接线:模型回 `render_chart` tool_call →
|
|
732
|
+
`area.call('render_chart', parseAiToolArgs(args))`。
|
|
733
|
+
|
|
734
|
+
### 13.6 `render_chart` 内置工具(schema 先行)
|
|
735
|
+
|
|
736
|
+
- inputSchema = ChartSpec JSON Schema(§13.7 接缝全数继承);另声明三个可选字段描述,
|
|
737
|
+
渲染层随图表重度场景落地(schema-ahead,不提前实现):`downsample?: boolean|number`
|
|
738
|
+
(true=自动上限,数字=最大保留点数)、`zoom?: 'x'|'brush'`、`marks?: array`(标记层
|
|
739
|
+
描述子)。`downsample` 的联合类型(type 数组)走 validateToolArgs 诚实清单。
|
|
654
740
|
|
|
655
|
-
### 13.
|
|
741
|
+
### 13.7 图表与 AI 族的接缝
|
|
656
742
|
|
|
657
743
|
- `render_chart` 工具:inputSchema = ChartSpec JSON Schema;run = renderChart(自动选型 /
|
|
658
744
|
容错归一 / 交互事件族 / 可隐藏图例全数继承)。
|
|
659
745
|
- **工具回执内嵌图**:`renderAiToolCall` 的 output 为 `{type:'chart', spec}` 信封或裸
|
|
660
|
-
ChartSpec 时,IO 区直接渲染小图(`chartSpecOf` 探测,`.ai-tool-io-chart` 容器)。
|
|
746
|
+
ChartSpec 时,IO 区直接渲染小图(`chartSpecOf` 探测,`.icen-ai-tool-io-chart` 容器)。
|
|
661
747
|
- **kind**:ai-core 内置 `chart` kind(折线图标,info tint,summarize 取 title);
|
|
662
748
|
`inferKind`:工具名含 chart/plot/visualize → 'chart'。
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
## 输入体验续修(2026-10-18)
|
|
752
|
+
|
|
753
|
+
资源目录、技能/工具分组、可取消上传适配、独立请求快照和键盘/视觉契约见 [AI 输入体验](ai-input-experience.md)。新增字段全部可选。纯文本没有额外 payload 字段;选中文件需要宿主 prepareMessage 上传适配,不会默认丢弃附件。
|