@icen.ai/ui 0.5.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/AGENTS.md +256 -0
  2. package/README.md +47 -13
  3. package/dist/base.css +31 -4
  4. package/dist/behaviors/accordion.d.mts +4 -0
  5. package/dist/behaviors/accordion.mjs +4 -3
  6. package/dist/behaviors/ai-chat.d.mts +57 -0
  7. package/dist/behaviors/ai-chat.mjs +557 -0
  8. package/dist/behaviors/ai-composer.d.mts +112 -0
  9. package/dist/behaviors/ai-composer.mjs +2032 -0
  10. package/dist/behaviors/ai-core.d.mts +151 -0
  11. package/dist/behaviors/ai-core.mjs +491 -0
  12. package/dist/behaviors/ai-diff.d.mts +53 -0
  13. package/dist/behaviors/ai-diff.mjs +282 -0
  14. package/dist/behaviors/ai-panel.d.mts +98 -0
  15. package/dist/behaviors/ai-panel.mjs +1012 -0
  16. package/dist/behaviors/ai-provider.d.mts +198 -0
  17. package/dist/behaviors/ai-provider.mjs +1055 -0
  18. package/dist/behaviors/ai-tool.d.mts +18 -0
  19. package/dist/behaviors/ai-tool.mjs +2125 -0
  20. package/dist/behaviors/ai-tools.d.mts +81 -0
  21. package/dist/behaviors/ai-tools.mjs +1658 -0
  22. package/dist/behaviors/back-top.d.mts +20 -0
  23. package/dist/behaviors/back-top.mjs +49 -32
  24. package/dist/behaviors/carousel.d.mts +4 -0
  25. package/dist/behaviors/carousel.mjs +34 -16
  26. package/dist/behaviors/charts.d.mts +250 -0
  27. package/dist/behaviors/charts.mjs +1301 -448
  28. package/dist/behaviors/command-palette.d.mts +8 -0
  29. package/dist/behaviors/command-palette.mjs +24 -12
  30. package/dist/behaviors/context-menu.d.mts +36 -0
  31. package/dist/behaviors/context-menu.mjs +6 -2
  32. package/dist/behaviors/copy.d.mts +4 -0
  33. package/dist/behaviors/copy.mjs +7 -6
  34. package/dist/behaviors/datatable.d.mts +78 -0
  35. package/dist/behaviors/datatable.mjs +54 -11
  36. package/dist/behaviors/date-picker.d.mts +4 -0
  37. package/dist/behaviors/date-picker.mjs +104 -36
  38. package/dist/behaviors/dropdown.d.mts +4 -0
  39. package/dist/behaviors/dropdown.mjs +7 -3
  40. package/dist/behaviors/input.d.mts +4 -0
  41. package/dist/behaviors/input.mjs +29 -22
  42. package/dist/behaviors/modal.d.mts +11 -0
  43. package/dist/behaviors/modal.mjs +11 -6
  44. package/dist/behaviors/nav.d.mts +7 -0
  45. package/dist/behaviors/nav.mjs +25 -9
  46. package/dist/behaviors/notification.d.mts +172 -0
  47. package/dist/behaviors/notification.mjs +243 -89
  48. package/dist/behaviors/popover.d.mts +46 -0
  49. package/dist/behaviors/popover.mjs +2 -1
  50. package/dist/behaviors/select.d.mts +4 -0
  51. package/dist/behaviors/select.mjs +131 -26
  52. package/dist/behaviors/sidebar.d.mts +4 -0
  53. package/dist/behaviors/sidebar.mjs +4 -3
  54. package/dist/behaviors/slider.d.mts +4 -0
  55. package/dist/behaviors/slider.mjs +4 -3
  56. package/dist/behaviors/split-pane.d.mts +4 -0
  57. package/dist/behaviors/split-pane.mjs +13 -13
  58. package/dist/behaviors/tabs.d.mts +8 -0
  59. package/dist/behaviors/tabs.mjs +44 -19
  60. package/dist/behaviors/tag-input.d.mts +4 -0
  61. package/dist/behaviors/tag-input.mjs +12 -3
  62. package/dist/behaviors/theme.d.mts +25 -0
  63. package/dist/behaviors/theme.mjs +3 -1
  64. package/dist/behaviors/toast.d.mts +26 -0
  65. package/dist/behaviors/toast.mjs +24 -18
  66. package/dist/behaviors/tree.d.mts +4 -0
  67. package/dist/behaviors/tree.mjs +4 -3
  68. package/dist/behaviors/upload.d.mts +4 -0
  69. package/dist/behaviors/upload.mjs +63 -26
  70. package/dist/components/ai-chat.css +1037 -0
  71. package/dist/components/ai-chat.mjs +3 -0
  72. package/dist/components/ai-composer.mjs +3 -0
  73. package/dist/components/ai-context.mjs +2 -0
  74. package/dist/components/ai-diff.css +311 -0
  75. package/dist/components/ai-diff.mjs +3 -0
  76. package/dist/components/ai-files.mjs +3 -0
  77. package/dist/components/ai-message.mjs +3 -0
  78. package/dist/components/ai-panel.css +682 -0
  79. package/dist/components/ai-reasoning.mjs +3 -0
  80. package/dist/components/ai-subagent.mjs +2 -0
  81. package/dist/components/ai-todo.mjs +2 -0
  82. package/dist/components/ai-tool-call.mjs +2 -0
  83. package/dist/components/ai-tool.css +532 -0
  84. package/dist/components/ai-usage.mjs +3 -0
  85. package/dist/components/badge.css +4 -8
  86. package/dist/components/breadcrumb.css +1 -5
  87. package/dist/components/btn.css +4 -7
  88. package/dist/components/card.css +3 -6
  89. package/dist/components/chart-bar.css +10 -0
  90. package/dist/components/chart-calendar.mjs +5 -0
  91. package/dist/components/chart-gauge.css +1 -1
  92. package/dist/components/chart-heatmap.css +40 -0
  93. package/dist/components/chart-scatter.css +24 -0
  94. package/dist/components/chart-scatter.mjs +4 -0
  95. package/dist/components/charts.css +86 -1
  96. package/dist/components/charts.mjs +1 -0
  97. package/dist/components/command-palette.css +2 -2
  98. package/dist/components/content.css +8 -9
  99. package/dist/components/copy.css +8 -8
  100. package/dist/components/datatable.css +4 -8
  101. package/dist/components/date-picker.css +3 -7
  102. package/dist/components/empty.css +0 -4
  103. package/dist/components/feedback.css +10 -17
  104. package/dist/components/form.css +13 -14
  105. package/dist/components/input.css +2 -2
  106. package/dist/components/layout.css +36 -36
  107. package/dist/components/media.css +12 -8
  108. package/dist/components/menu.css +2 -2
  109. package/dist/components/modal.css +1 -1
  110. package/dist/components/nav.css +11 -6
  111. package/dist/components/notification.css +28 -28
  112. package/dist/components/pagination.css +1 -1
  113. package/dist/components/panel.css +16 -16
  114. package/dist/components/pill.css +15 -15
  115. package/dist/components/segmented.css +5 -5
  116. package/dist/components/segmented.mjs +1 -0
  117. package/dist/components/select.css +12 -20
  118. package/dist/components/sidebar.css +1 -1
  119. package/dist/components/split-pane.css +0 -1
  120. package/dist/components/stat.css +14 -18
  121. package/dist/components/steps.css +2 -2
  122. package/dist/components/switch.css +8 -12
  123. package/dist/components/table.css +46 -47
  124. package/dist/components/tabs.css +35 -46
  125. package/dist/components/tag-input.css +4 -4
  126. package/dist/components/toast.css +16 -13
  127. package/dist/components/tooltip.css +2 -2
  128. package/dist/index.d.mts +37 -0
  129. package/dist/index.mjs +7890 -1199
  130. package/dist/registry.json +113 -4
  131. package/dist/retro-effects.css +21 -17
  132. package/dist/tokens/colors.css +4 -1
  133. package/dist/tokens/retro-effects.css +21 -17
  134. package/dist/tokens/style-profiles.css +21 -0
  135. package/dist/tokens.css +25 -1
  136. package/dist/ui.css +3168 -433
  137. package/docs/spec/ai-native.md +659 -0
  138. package/package.json +15 -5
@@ -0,0 +1,1037 @@
1
+ /*
2
+ * @icen.ai/ui — Component: ai-chat(AI 原生组件族:ai-chat / ai-message / ai-reasoning / ai-composer)
3
+ *
4
+ * 完整契约:
5
+ * <div class="ai-chat" data-density="normal"> ← verbose|normal|summary 三档
6
+ * <div class="ai-chat-scroll">
7
+ * <div class="ai-msg ai-msg--user|ai-msg--assistant|ai-msg--system|ai-msg--tool">
8
+ * <span class="ai-msg-avatar"></span>
9
+ * <div class="ai-msg-body">…消费方自行渲染 markdown…</div>
10
+ * <div class="ai-msg-actions"><button data-ai-msg-action="copy|retry">…</button></div>
11
+ * <div class="ai-msg-meta">12:04 · 1.2k tok</div>
12
+ * </div>
13
+ * </div>
14
+ * <button class="ai-chat-jump" hidden>回到底部 · N 条新消息</button>
15
+ * </div>
16
+ *
17
+ * <div class="ai-reasoning [.is-streaming|.is-done]">
18
+ * <button class="ai-reasoning-head" aria-expanded="false">
19
+ * <span class="ai-reasoning-label">思考过程</span><span class="ai-reasoning-time">3s</span>
20
+ * </button>
21
+ * <div class="ai-reasoning-body" hidden>…</div>
22
+ * </div>
23
+ *
24
+ * <div class="ai-composer" data-ai-composer [.is-running]>
25
+ * <div class="ai-composer-queue" hidden><span class="ai-composer-chip">
26
+ * <span class="ai-composer-chip-text">…</span>
27
+ * <button class="ai-composer-chip-remove" aria-label="移除">×</button>
28
+ * </span></div>
29
+ * <div class="ai-composer-refs" hidden><!-- @ 引用 chips,结构同上(v2 setComposerRefs) --></div>
30
+ * <div class="ai-composer-attach" hidden><!-- 附件 chips,结构同上 --></div>
31
+ * <div class="ai-composer-box control">
32
+ * <textarea class="ai-composer-input" rows="1" placeholder="…"></textarea>
33
+ * <div class="ai-composer-actions"><!-- v1 旧结构(无 toolbar 配置时保留): -->
34
+ * <button class="ai-composer-btn" data-ai-attach aria-label="附件"></button>
35
+ * <button class="ai-composer-send" data-ai-send aria-label="发送"></button> ← 运行态 .is-stop
36
+ * </div>
37
+ * <div class="ai-composer-toolbar"><!-- v2 工具条(setComposerModels/Usage 配置时 JS 装配):
38
+ * <button class="ai-composer-model" data-ai-model-open>provider 图标/首字母章 + 模型名 + chevron</button>
39
+ * <span class="ai-composer-spacer"></span>
40
+ * <span class="ai-composer-usage" data-ai-usage></span> ← 用量环挂载点(renderAiUsageRing)
41
+ * <button class="ai-composer-btn" data-ai-attach></button>
42
+ * <button class="ai-composer-send" data-ai-send></button> ← 运行态 .is-stop
43
+ * </div>
44
+ * </div>
45
+ * </div>
46
+ * 弹层 .ai-composer-popup(portal body,JS 动态创建):模型切换 / 斜杠命令 / @ 引用
47
+ * 三模式共用基座——.ai-composer-popup-search 搜索框、.ai-composer-popup-list(listbox)
48
+ * > .ai-composer-popup-group > .ai-composer-popup-group-label + .ai-composer-popup-option(role=option),
49
+ * .ai-composer-popup-empty 空态、.ai-composer-popup-foot 底部提示。
50
+ *
51
+ * 配套 behaviors:behaviors/ai-chat.ts(initAiChat / createAiStream)
52
+ * + behaviors/ai-composer.ts(initAiComposer / setComposerRunning
53
+ * + v2:setComposerModels / setComposerCommands / setComposerRefs / setComposerUsage)。
54
+ * 状态机 7 态 .is-* 中的 streaming/done 在本文件消费;running 脉冲点等条目态见 ai-tool.css(.ai-item 基元)。
55
+ * data-density:summary 档隐藏 meta、默认折叠 reasoning(纯 CSS);
56
+ * tool/system 类次要消息在 summary 档压淡。verbose 与 normal 目前同视觉(透明度两档生效)。
57
+ * 流式光标复用 tokens 的 blink keyframes(.animate-blink 同款);reasoning shimmer 时长走
58
+ * --ai-shimmer-duration 局部变量(默认值 1.6s)。
59
+ */
60
+
61
+ /* ══════════════ ai-chat(滚动容器 + 钉底) ══════════════ */
62
+
63
+ /* 输出容器宽度自稳定契约:即使宿主是 flex/grid 居中上下文(shrink-wrap 陷阱),
64
+ 也不随内容多少改变宽度——流式内容只允许纵向生长。
65
+ (用 align-self 而非 width:100%——width 在 shrink-wrap 父级下会塌成 min-content) */
66
+ .ai-chat,
67
+ .ai-reasoning,
68
+ .ai-composer {
69
+ align-self: stretch;
70
+ }
71
+
72
+ .ai-chat {
73
+ position: relative;
74
+ display: flex;
75
+ flex-direction: column;
76
+ height: 100%;
77
+ min-height: 0;
78
+ gap: var(--density-space-sm);
79
+ }
80
+
81
+ .ai-chat-scroll {
82
+ flex: 1;
83
+ min-height: 0;
84
+ overflow-y: auto;
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: var(--density-space-md);
88
+ padding: var(--density-space-xs) var(--density-space-2xs, 2px);
89
+ scroll-behavior: smooth;
90
+ }
91
+
92
+ /* 「回到底部 · N 条新消息」浮动钮(用户上滚暂停跟随时出现,initAiChat 驱动) */
93
+ .ai-chat-jump {
94
+ position: absolute;
95
+ left: 50%;
96
+ bottom: var(--density-space-md);
97
+ transform: translateX(-50%);
98
+ z-index: var(--z-raised);
99
+ display: inline-flex;
100
+ align-items: center;
101
+ gap: var(--density-space-xs);
102
+ padding: var(--density-space-xs) var(--density-space-md);
103
+ border: 1px solid var(--token-line-soft);
104
+ border-radius: var(--radius-full);
105
+ background: var(--token-surface);
106
+ box-shadow: var(--token-shadow-card);
107
+ color: var(--token-text-muted);
108
+ font-family: var(--font-mono);
109
+ font-size: var(--density-font-size-xs);
110
+ letter-spacing: 0.02em;
111
+ white-space: nowrap;
112
+ cursor: pointer;
113
+ animation: icen-pop-in var(--ai-jump-enter-duration, var(--duration-normal)) var(--ease-out);
114
+ transition:
115
+ color var(--icen-tx-fast),
116
+ border-color var(--icen-tx-fast),
117
+ box-shadow var(--icen-tx-fast);
118
+ }
119
+ .ai-chat-jump:hover {
120
+ color: var(--token-accent);
121
+ border-color: var(--icen-hover-border);
122
+ }
123
+ .ai-chat-jump:focus-visible {
124
+ outline: none;
125
+ box-shadow: var(--icen-ring);
126
+ }
127
+ .ai-chat-jump[hidden] {
128
+ display: none;
129
+ }
130
+
131
+ /* ══════════════ ai-message ══════════════ */
132
+
133
+ .ai-msg {
134
+ position: relative;
135
+ display: grid;
136
+ grid-template-columns: auto 1fr;
137
+ gap: var(--density-space-sm);
138
+ padding: var(--density-space-sm) var(--density-space-md);
139
+ border-radius: var(--radius-md);
140
+ animation: icen-pop-in var(--ai-msg-enter-duration, var(--duration-normal)) var(--ease-out);
141
+ }
142
+
143
+ .ai-msg-avatar {
144
+ width: 28px;
145
+ height: 28px;
146
+ flex-shrink: 0;
147
+ display: inline-flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ border-radius: var(--radius-sm);
151
+ background: var(--token-fill-2);
152
+ color: var(--token-text-muted);
153
+ font-family: var(--font-mono);
154
+ font-size: var(--density-font-size-2xs);
155
+ letter-spacing: 0.04em;
156
+ user-select: none;
157
+ }
158
+ .ai-msg--user .ai-msg-avatar {
159
+ background: color-mix(in srgb, var(--token-accent) 14%, transparent);
160
+ color: var(--token-accent);
161
+ }
162
+ .ai-msg--assistant .ai-msg-avatar {
163
+ background: color-mix(in srgb, var(--token-success) 12%, transparent);
164
+ color: var(--token-success);
165
+ }
166
+ .ai-msg--system .ai-msg-avatar {
167
+ background: color-mix(in srgb, var(--token-warning) 12%, transparent);
168
+ color: var(--token-warning);
169
+ }
170
+ .ai-msg--tool .ai-msg-avatar {
171
+ background: color-mix(in srgb, var(--token-info) 12%, transparent);
172
+ color: var(--token-info);
173
+ }
174
+
175
+ .ai-msg-body {
176
+ min-width: 0;
177
+ min-height: 1.6em; /* 流式从空开始时占住一行高度,不塌 */
178
+ font-size: var(--density-font-size-md);
179
+ line-height: 1.6;
180
+ white-space: pre-wrap;
181
+ word-break: break-word;
182
+ }
183
+
184
+ /* 流式光标(▍闪烁,复用 tokens 的 blink keyframes,同 .animate-blink) */
185
+ .ai-msg-body.is-streaming::after,
186
+ .ai-reasoning-body.is-streaming::after {
187
+ content: '▍';
188
+ margin-left: 1px;
189
+ color: var(--token-accent);
190
+ animation: blink 1s step-end infinite;
191
+ }
192
+
193
+ /* 消息操作:hover / focus-within 才出现(键盘可达等价) */
194
+ .ai-msg-actions {
195
+ position: absolute;
196
+ top: var(--density-space-xs);
197
+ right: var(--density-space-xs);
198
+ display: flex;
199
+ gap: 2px;
200
+ padding: 2px;
201
+ border-radius: var(--radius-sm);
202
+ background: var(--token-surface);
203
+ border: 1px solid var(--token-line-soft);
204
+ opacity: 0;
205
+ visibility: hidden;
206
+ transform: translateY(-2px);
207
+ transition:
208
+ opacity var(--icen-tx-fast),
209
+ transform var(--icen-tx-fast),
210
+ visibility var(--icen-tx-fast);
211
+ }
212
+ .ai-msg:hover .ai-msg-actions,
213
+ .ai-msg:focus-within .ai-msg-actions {
214
+ opacity: 1;
215
+ visibility: visible;
216
+ transform: translateY(0);
217
+ }
218
+ .ai-msg-actions [data-ai-msg-action] {
219
+ display: inline-flex;
220
+ align-items: center;
221
+ justify-content: center;
222
+ width: 22px;
223
+ height: 22px;
224
+ padding: 0;
225
+ border: 0;
226
+ border-radius: var(--radius-xs);
227
+ background: transparent;
228
+ color: var(--token-text-muted);
229
+ cursor: pointer;
230
+ transition:
231
+ color var(--icen-tx-fast),
232
+ background-color var(--icen-tx-fast);
233
+ }
234
+ .ai-msg-actions [data-ai-msg-action]:hover {
235
+ color: var(--token-accent);
236
+ background: var(--token-fill-2);
237
+ }
238
+ .ai-msg-actions [data-ai-msg-action]:focus-visible {
239
+ outline: none;
240
+ box-shadow: var(--icen-ring);
241
+ }
242
+ .ai-msg-actions [data-ai-msg-action] svg {
243
+ width: 12px;
244
+ height: 12px;
245
+ }
246
+
247
+ /* meta:时间 · token 数(summary 档隐藏) */
248
+ .ai-msg-meta {
249
+ margin-top: 2px;
250
+ font-family: var(--font-mono);
251
+ font-size: var(--density-font-size-2xs);
252
+ color: var(--token-text-faint);
253
+ font-variant-numeric: tabular-nums;
254
+ }
255
+
256
+ /* ── 多模态消息部件(renderAiMessage,spec §10)── */
257
+
258
+ .ai-msg-text {
259
+ display: block;
260
+ }
261
+
262
+ /* 图片 / 音频 / 视频:宽度自稳定(不超正文列),纵向生长 */
263
+ .ai-msg-media {
264
+ display: block;
265
+ max-width: min(100%, 360px);
266
+ margin: var(--density-space-xs) 0;
267
+ border: 1px solid var(--token-line-soft);
268
+ border-radius: var(--radius-md);
269
+ background: var(--token-surface);
270
+ }
271
+ .ai-msg-media--image {
272
+ max-height: 320px;
273
+ object-fit: contain;
274
+ }
275
+ .ai-msg-media--audio {
276
+ width: min(100%, 300px);
277
+ height: 40px;
278
+ }
279
+ .ai-msg-media--video {
280
+ max-width: min(100%, 480px);
281
+ max-height: 300px;
282
+ }
283
+
284
+ /* 文件 chip(含可选下载链接) */
285
+ .ai-msg-file {
286
+ display: inline-flex;
287
+ align-items: center;
288
+ gap: var(--density-space-xs);
289
+ margin: 2px var(--density-space-xs) 2px 0;
290
+ padding: 2px var(--density-space-sm);
291
+ border: 1px solid var(--token-line);
292
+ border-radius: var(--radius-sm);
293
+ background: var(--token-surface);
294
+ font-family: var(--font-mono);
295
+ font-size: var(--density-font-size-xs);
296
+ color: var(--token-text);
297
+ }
298
+ .ai-msg-file-link {
299
+ color: var(--token-accent);
300
+ text-decoration: none;
301
+ }
302
+ .ai-msg-file-link:hover {
303
+ text-decoration: underline;
304
+ }
305
+
306
+ /* MCP ResourceLink 等资源引用 */
307
+ .ai-msg-link {
308
+ color: var(--token-accent);
309
+ }
310
+
311
+ /* assistant 消息的模型名标签 */
312
+ .ai-msg-model {
313
+ display: inline-block;
314
+ margin-left: var(--density-space-xs);
315
+ padding: 0 6px;
316
+ border-radius: var(--radius-xs);
317
+ background: var(--token-fill-1);
318
+ color: var(--token-text-faint);
319
+ font-family: var(--font-mono);
320
+ font-size: var(--density-font-size-2xs);
321
+ vertical-align: middle;
322
+ }
323
+
324
+ /* ── 错误变体(renderAiMessage.setError / createAiStream.fail)── */
325
+
326
+ .ai-msg--error .ai-msg-body {
327
+ border-left: 2px solid var(--token-error);
328
+ padding-left: var(--density-space-sm);
329
+ }
330
+ .ai-msg-error-text {
331
+ display: block;
332
+ margin-top: var(--density-space-xs);
333
+ color: var(--token-error);
334
+ font-size: var(--density-font-size-xs);
335
+ }
336
+
337
+ /* ══════════════ ai-reasoning ══════════════ */
338
+
339
+ .ai-reasoning {
340
+ margin: var(--density-space-xs) 0;
341
+ padding-left: var(--density-space-md);
342
+ border-left: 2px solid var(--token-line-soft);
343
+ }
344
+
345
+ .ai-reasoning-head {
346
+ display: flex;
347
+ align-items: center;
348
+ gap: var(--density-space-sm);
349
+ width: 100%;
350
+ padding: var(--density-space-xs) 0;
351
+ border: 0;
352
+ background: none;
353
+ color: var(--token-text-muted);
354
+ font-family: var(--font-mono);
355
+ font-size: var(--density-font-size-sm);
356
+ letter-spacing: 0.02em;
357
+ cursor: pointer;
358
+ text-align: left;
359
+ }
360
+ .ai-reasoning-head:hover {
361
+ color: var(--token-text);
362
+ }
363
+ .ai-reasoning-head:focus-visible {
364
+ outline: none;
365
+ box-shadow: var(--icen-ring);
366
+ border-radius: var(--radius-xs);
367
+ }
368
+
369
+ /* 折叠指示 chevron(CSS 三角,展开时旋转) */
370
+ .ai-reasoning-head::before {
371
+ content: '';
372
+ flex-shrink: 0;
373
+ width: 6px;
374
+ height: 6px;
375
+ border-right: 1.5px solid currentColor;
376
+ border-bottom: 1.5px solid currentColor;
377
+ transform: rotate(-45deg);
378
+ transition: transform var(--icen-tx-fast);
379
+ }
380
+ .ai-reasoning:has(.ai-reasoning-head[aria-expanded='true']) .ai-reasoning-head::before {
381
+ transform: rotate(45deg);
382
+ }
383
+
384
+ .ai-reasoning-label {
385
+ overflow: hidden;
386
+ text-overflow: ellipsis;
387
+ white-space: nowrap;
388
+ }
389
+
390
+ .ai-reasoning-time {
391
+ margin-left: auto;
392
+ font-size: var(--density-font-size-2xs);
393
+ color: var(--token-text-faint);
394
+ font-variant-numeric: tabular-nums;
395
+ }
396
+
397
+ /* 流式中:耗时位换成 shimmer「正在思考…」(时长走局部变量) */
398
+ .ai-reasoning.is-streaming .ai-reasoning-time {
399
+ display: none;
400
+ }
401
+ .ai-reasoning.is-streaming .ai-reasoning-head::after {
402
+ content: '正在思考…';
403
+ margin-left: auto;
404
+ font-size: var(--density-font-size-2xs);
405
+ background: linear-gradient(
406
+ 90deg,
407
+ var(--token-text-faint) 25%,
408
+ var(--token-text) 50%,
409
+ var(--token-text-faint) 75%
410
+ );
411
+ background-size: 200% 100%;
412
+ -webkit-background-clip: text;
413
+ background-clip: text;
414
+ -webkit-text-fill-color: transparent;
415
+ color: transparent;
416
+ animation: ai-shimmer var(--ai-shimmer-duration, 1.6s) linear infinite;
417
+ }
418
+
419
+ .ai-reasoning-body {
420
+ padding: var(--density-space-xs) 0 var(--density-space-sm);
421
+ color: var(--token-text-muted);
422
+ font-size: var(--density-font-size-sm);
423
+ line-height: 1.6;
424
+ white-space: pre-wrap;
425
+ word-break: break-word;
426
+ }
427
+ .ai-reasoning-body[hidden] {
428
+ display: none;
429
+ }
430
+
431
+ /* ══════════════ ai-composer ══════════════ */
432
+
433
+ .ai-composer {
434
+ display: flex;
435
+ flex-direction: column;
436
+ gap: var(--density-space-xs);
437
+ padding: var(--density-space-sm);
438
+ border: 1px solid var(--token-line-soft);
439
+ border-radius: var(--radius-lg);
440
+ background: var(--token-surface);
441
+ box-shadow: var(--token-shadow-card);
442
+ }
443
+ .ai-composer:focus-within {
444
+ border-color: var(--icen-hover-border);
445
+ }
446
+
447
+ /* 排队 / 引用 / 附件 chips 行 */
448
+ .ai-composer-queue,
449
+ .ai-composer-refs,
450
+ .ai-composer-attach {
451
+ display: flex;
452
+ flex-wrap: wrap;
453
+ gap: var(--density-space-xs);
454
+ }
455
+ .ai-composer-queue[hidden],
456
+ .ai-composer-refs[hidden],
457
+ .ai-composer-attach[hidden] {
458
+ display: none;
459
+ }
460
+
461
+ .ai-composer-chip {
462
+ display: inline-flex;
463
+ align-items: center;
464
+ gap: 4px;
465
+ max-width: 240px;
466
+ padding: 2px var(--density-space-xs);
467
+ border: 1px solid var(--token-line-soft);
468
+ border-radius: var(--radius-full);
469
+ background: var(--token-surface-strong);
470
+ color: var(--token-text-muted);
471
+ font-size: var(--density-font-size-xs);
472
+ line-height: 1.4;
473
+ }
474
+ .ai-composer-chip-text {
475
+ overflow: hidden;
476
+ text-overflow: ellipsis;
477
+ white-space: nowrap;
478
+ }
479
+ .ai-composer-chip-remove {
480
+ display: inline-flex;
481
+ align-items: center;
482
+ justify-content: center;
483
+ width: 14px;
484
+ height: 14px;
485
+ padding: 0;
486
+ border: 0;
487
+ border-radius: var(--radius-full);
488
+ background: transparent;
489
+ color: var(--token-text-faint);
490
+ font-size: 12px;
491
+ line-height: 1;
492
+ cursor: pointer;
493
+ transition:
494
+ color var(--icen-tx-fast),
495
+ background-color var(--icen-tx-fast);
496
+ }
497
+ .ai-composer-chip-remove:hover {
498
+ color: var(--token-error);
499
+ background: var(--token-error-muted);
500
+ }
501
+ .ai-composer-chip-remove:focus-visible {
502
+ outline: none;
503
+ box-shadow: var(--icen-ring);
504
+ }
505
+
506
+ /* 输入框(.control 基元提供 hover/focus 边框与光晕);flex-wrap 让工具条独占下一行 */
507
+ .ai-composer-box {
508
+ display: flex;
509
+ flex-wrap: wrap;
510
+ align-items: flex-end;
511
+ gap: var(--density-space-sm);
512
+ padding: var(--density-space-xs) var(--density-space-sm);
513
+ border: 1px solid var(--token-line-soft);
514
+ border-radius: var(--radius-md);
515
+ background: var(--token-panel);
516
+ box-shadow: var(--token-shadow-inset);
517
+ }
518
+
519
+ .ai-composer-input {
520
+ flex: 1;
521
+ min-width: 0;
522
+ padding: var(--density-space-xs) 0;
523
+ border: 0;
524
+ outline: none;
525
+ background: transparent;
526
+ resize: none;
527
+ font-family: inherit;
528
+ font-size: var(--density-font-size-md);
529
+ line-height: 1.5;
530
+ max-height: 12em;
531
+ overflow-y: auto;
532
+ }
533
+
534
+ /* v1 旧结构按钮行(无 toolbar 配置时保留);v2 中按钮被移入工具条后此容器由 JS 移除 */
535
+ .ai-composer-actions {
536
+ display: flex;
537
+ align-items: center;
538
+ gap: var(--density-space-xs);
539
+ flex-shrink: 0;
540
+ }
541
+
542
+ /* ── v2 工具条(composer-box 内底部,业界主流形态)── */
543
+
544
+ .ai-composer-toolbar {
545
+ flex: 0 0 100%;
546
+ display: flex;
547
+ align-items: center;
548
+ gap: var(--density-space-xs);
549
+ padding-top: var(--density-space-xs);
550
+ border-top: 1px solid var(--token-line-soft);
551
+ }
552
+ .ai-composer-toolbar[hidden] {
553
+ display: none;
554
+ }
555
+
556
+ .ai-composer-spacer {
557
+ flex: 1;
558
+ }
559
+
560
+ /* 模型切换钮:provider 图标/首字母章 + 模型名 + chevron */
561
+ .ai-composer-model {
562
+ display: inline-flex;
563
+ align-items: center;
564
+ gap: 4px;
565
+ max-width: 180px;
566
+ height: 28px;
567
+ padding: 0 var(--density-space-xs);
568
+ border: 1px solid transparent;
569
+ border-radius: var(--radius-sm);
570
+ background: transparent;
571
+ color: var(--token-text-muted);
572
+ font-size: var(--density-font-size-xs);
573
+ cursor: pointer;
574
+ transition:
575
+ color var(--icen-tx-fast),
576
+ background-color var(--icen-tx-fast);
577
+ }
578
+ .ai-composer-model:hover {
579
+ color: var(--token-text);
580
+ background: var(--token-fill-2);
581
+ }
582
+ .ai-composer-model:focus-visible {
583
+ outline: none;
584
+ box-shadow: var(--icen-ring);
585
+ }
586
+ .ai-composer-model-icon,
587
+ .ai-composer-model-provider-icon {
588
+ display: inline-flex;
589
+ align-items: center;
590
+ justify-content: center;
591
+ flex-shrink: 0;
592
+ }
593
+ .ai-composer-model-provider-icon svg {
594
+ width: 14px;
595
+ height: 14px;
596
+ }
597
+ .ai-composer-model-name {
598
+ overflow: hidden;
599
+ text-overflow: ellipsis;
600
+ white-space: nowrap;
601
+ }
602
+ .ai-composer-model-chevron {
603
+ display: inline-flex;
604
+ align-items: center;
605
+ justify-content: center;
606
+ flex-shrink: 0;
607
+ color: var(--token-text-faint);
608
+ transition: transform var(--icen-tx-fast);
609
+ }
610
+ .ai-composer-model-chevron svg {
611
+ width: 12px;
612
+ height: 12px;
613
+ }
614
+ .ai-composer-model[aria-expanded='true'] .ai-composer-model-chevron {
615
+ transform: rotate(180deg);
616
+ }
617
+
618
+ /* provider 图标缺失时的首字母圆形章(token 色) */
619
+ .ai-composer-model-badge {
620
+ display: inline-flex;
621
+ align-items: center;
622
+ justify-content: center;
623
+ width: 16px;
624
+ height: 16px;
625
+ border-radius: var(--radius-full);
626
+ background: color-mix(in srgb, var(--token-accent) 15%, transparent);
627
+ color: var(--token-accent);
628
+ font-size: 10px;
629
+ font-weight: 600;
630
+ line-height: 1;
631
+ user-select: none;
632
+ }
633
+ .ai-composer-model-icon .ai-composer-model-badge {
634
+ width: 14px;
635
+ height: 14px;
636
+ font-size: 9px;
637
+ }
638
+
639
+ /* 用量环挂载点(环本体 .ai-usage-ring 在 ai-panel.css) */
640
+ .ai-composer-usage {
641
+ display: inline-flex;
642
+ align-items: center;
643
+ flex-shrink: 0;
644
+ }
645
+ .ai-composer-usage:empty {
646
+ display: none;
647
+ }
648
+
649
+ /* ── v2 弹层(portal body,模型/命令/引用三模式共用基座;定位 inline 由 behavior 写入)── */
650
+
651
+ .ai-composer-popup {
652
+ position: fixed;
653
+ z-index: var(--z-popover);
654
+ display: flex;
655
+ flex-direction: column;
656
+ padding: var(--density-space-2xs);
657
+ border: 1px solid var(--token-line-soft);
658
+ border-radius: var(--radius-md);
659
+ background: var(--token-card-strong);
660
+ box-shadow: var(--token-shadow-popover, var(--token-shadow-card));
661
+ overflow: hidden;
662
+ animation: icen-pop-in var(--ai-composer-popup-enter-duration, var(--duration-normal)) var(--ease-out);
663
+ }
664
+
665
+ /* 搜索框(模型/引用过滤) */
666
+ .ai-composer-popup-search {
667
+ flex-shrink: 0;
668
+ padding: var(--density-space-2xs);
669
+ border-bottom: 1px solid var(--token-line-soft);
670
+ }
671
+ .ai-composer-popup-search-input {
672
+ width: 100%;
673
+ padding: var(--density-space-2xs) var(--density-space-xs);
674
+ border: 1px solid var(--token-line-soft);
675
+ border-radius: var(--radius-sm);
676
+ background: var(--token-panel);
677
+ color: var(--token-text);
678
+ font-size: var(--density-font-size-sm);
679
+ outline: none;
680
+ transition: border-color var(--icen-tx-fast);
681
+ }
682
+ .ai-composer-popup-search-input:focus {
683
+ border-color: var(--token-accent);
684
+ }
685
+ .ai-composer-popup-search-input::placeholder {
686
+ color: var(--token-text-faint);
687
+ }
688
+
689
+ /* 列表(max-height inline 由 behavior 按 computePopoverLayout 写入) */
690
+ .ai-composer-popup-list {
691
+ flex: 1;
692
+ min-height: 0;
693
+ overflow-y: auto;
694
+ overscroll-behavior: contain;
695
+ padding: var(--density-space-2xs);
696
+ }
697
+
698
+ /* 分组(provider / ref kind) */
699
+ .ai-composer-popup-group[hidden] {
700
+ display: none;
701
+ }
702
+ .ai-composer-popup-group-label {
703
+ display: flex;
704
+ align-items: center;
705
+ gap: 4px;
706
+ padding: var(--density-space-xs) var(--density-space-xs) var(--density-space-2xs);
707
+ font-size: var(--density-font-size-2xs);
708
+ color: var(--token-text-faint);
709
+ letter-spacing: 0.02em;
710
+ }
711
+ .ai-composer-popup-group-icon {
712
+ display: inline-flex;
713
+ align-items: center;
714
+ justify-content: center;
715
+ flex-shrink: 0;
716
+ color: var(--token-text-muted);
717
+ }
718
+ .ai-composer-popup-group-icon svg {
719
+ width: 12px;
720
+ height: 12px;
721
+ }
722
+ .ai-composer-popup-group-icon .ai-composer-model-badge {
723
+ width: 12px;
724
+ height: 12px;
725
+ font-size: 8px;
726
+ }
727
+
728
+ /* 选项行(label + meta + check) */
729
+ .ai-composer-popup-option {
730
+ display: flex;
731
+ align-items: center;
732
+ gap: var(--density-space-xs);
733
+ width: 100%;
734
+ padding: var(--density-space-xs);
735
+ border: 0;
736
+ border-radius: var(--radius-sm);
737
+ background: transparent;
738
+ color: var(--token-text);
739
+ font-size: var(--density-font-size-sm);
740
+ text-align: left;
741
+ cursor: pointer;
742
+ transition: background-color var(--icen-tx-fast);
743
+ }
744
+ .ai-composer-popup-option:hover {
745
+ background: var(--token-fill-2);
746
+ }
747
+ .ai-composer-popup-option.is-focused {
748
+ background: var(--token-fill-2);
749
+ }
750
+ .ai-composer-popup-option:focus-visible {
751
+ outline: none;
752
+ box-shadow: var(--icen-ring);
753
+ }
754
+ .ai-composer-popup-option-icon {
755
+ display: inline-flex;
756
+ align-items: center;
757
+ justify-content: center;
758
+ flex-shrink: 0;
759
+ color: var(--token-text-muted);
760
+ }
761
+ .ai-composer-popup-option-icon svg {
762
+ width: 14px;
763
+ height: 14px;
764
+ }
765
+ .ai-composer-popup-option-icon .ai-composer-model-badge {
766
+ width: 14px;
767
+ height: 14px;
768
+ font-size: 9px;
769
+ }
770
+ .ai-composer-popup-option-label {
771
+ overflow: hidden;
772
+ text-overflow: ellipsis;
773
+ white-space: nowrap;
774
+ }
775
+ .ai-composer-popup-option-sub {
776
+ margin-left: 6px;
777
+ font-size: var(--density-font-size-2xs);
778
+ color: var(--token-text-faint);
779
+ }
780
+ .ai-composer-popup-option-meta {
781
+ margin-left: auto;
782
+ flex-shrink: 0;
783
+ font-family: var(--font-mono);
784
+ font-size: var(--density-font-size-2xs);
785
+ color: var(--token-text-faint);
786
+ font-variant-numeric: tabular-nums;
787
+ }
788
+ .ai-composer-popup-option-check {
789
+ display: none;
790
+ flex-shrink: 0;
791
+ color: var(--token-accent);
792
+ }
793
+ .ai-composer-popup-option-check svg {
794
+ width: 12px;
795
+ height: 12px;
796
+ }
797
+ .ai-composer-popup-option[aria-selected='true'] .ai-composer-popup-option-check {
798
+ display: inline-flex;
799
+ }
800
+
801
+ /* 命令行:/name <argsHint> + 描述 */
802
+ .ai-composer-popup-cmd {
803
+ flex-direction: column;
804
+ align-items: flex-start;
805
+ gap: 1px;
806
+ }
807
+ .ai-composer-popup-cmd-line {
808
+ display: flex;
809
+ align-items: baseline;
810
+ gap: 6px;
811
+ max-width: 100%;
812
+ }
813
+ .ai-composer-popup-cmd-name {
814
+ font-family: var(--font-mono);
815
+ color: var(--token-accent);
816
+ }
817
+ .ai-composer-popup-cmd-args {
818
+ font-family: var(--font-mono);
819
+ font-size: var(--density-font-size-2xs);
820
+ color: var(--token-text-faint);
821
+ }
822
+ .ai-composer-popup-cmd-desc {
823
+ font-size: var(--density-font-size-2xs);
824
+ color: var(--token-text-muted);
825
+ }
826
+
827
+ /* 空态 */
828
+ .ai-composer-popup-empty {
829
+ flex-shrink: 0;
830
+ padding: var(--density-space-md);
831
+ text-align: center;
832
+ font-size: var(--density-font-size-sm);
833
+ color: var(--token-text-faint);
834
+ }
835
+ .ai-composer-popup-empty[hidden] {
836
+ display: none;
837
+ }
838
+
839
+ /* 底部提示(模型弹层:切换模型会使 prompt 缓存失效;cacheRead 占比高时 warning 色) */
840
+ .ai-composer-popup-foot {
841
+ flex-shrink: 0;
842
+ display: flex;
843
+ align-items: center;
844
+ gap: 4px;
845
+ padding: var(--density-space-xs);
846
+ border-top: 1px solid var(--token-line-soft);
847
+ font-size: var(--density-font-size-2xs);
848
+ color: var(--token-text-faint);
849
+ }
850
+ .ai-composer-popup-foot svg {
851
+ width: 12px;
852
+ height: 12px;
853
+ flex-shrink: 0;
854
+ }
855
+ .ai-composer-popup-foot.is-warn {
856
+ color: var(--token-warning);
857
+ }
858
+
859
+ /* ── 工具条按钮统一 28px 方形、svg 14px 居中(v1 旧结构按钮同样收敛)── */
860
+
861
+ .ai-composer-btn {
862
+ display: inline-flex;
863
+ align-items: center;
864
+ justify-content: center;
865
+ width: 28px;
866
+ height: 28px;
867
+ padding: 0;
868
+ border: 1px solid transparent;
869
+ border-radius: var(--radius-sm);
870
+ background: transparent;
871
+ color: var(--token-text-muted);
872
+ cursor: pointer;
873
+ transition:
874
+ color var(--icen-tx-fast),
875
+ background-color var(--icen-tx-fast);
876
+ }
877
+ .ai-composer-btn:hover {
878
+ color: var(--token-text);
879
+ background: var(--token-fill-2);
880
+ }
881
+ .ai-composer-btn:focus-visible {
882
+ outline: none;
883
+ box-shadow: var(--icen-ring);
884
+ }
885
+ .ai-composer-btn svg {
886
+ width: 14px;
887
+ height: 14px;
888
+ }
889
+
890
+ /* 发送钮:accent 实底;运行态 .is-stop 变停止钮(中性底 + error 色方标,停语气但不整钮铺红) */
891
+ .ai-composer-send {
892
+ display: inline-flex;
893
+ align-items: center;
894
+ justify-content: center;
895
+ width: 28px;
896
+ height: 28px;
897
+ padding: 0;
898
+ border: 1px solid transparent;
899
+ border-radius: var(--radius-sm);
900
+ background: var(--token-accent);
901
+ color: var(--token-on-accent);
902
+ cursor: pointer;
903
+ transition:
904
+ background-color var(--icen-tx-fast),
905
+ color var(--icen-tx-fast),
906
+ border-color var(--icen-tx-fast);
907
+ }
908
+ .ai-composer-send:hover {
909
+ background: var(--token-accent-deep, var(--token-accent));
910
+ }
911
+ .ai-composer-send:focus-visible {
912
+ outline: none;
913
+ box-shadow: var(--icen-ring);
914
+ }
915
+ .ai-composer-send svg {
916
+ width: 14px;
917
+ height: 14px;
918
+ }
919
+ .ai-composer-send .ai-composer-send-icon[hidden] {
920
+ display: none;
921
+ }
922
+ .ai-composer-send.is-stop {
923
+ background: var(--token-surface-strong);
924
+ color: var(--token-error);
925
+ border-color: color-mix(in srgb, var(--token-error) 35%, transparent);
926
+ }
927
+ .ai-composer-send.is-stop:hover {
928
+ background: var(--token-error-muted);
929
+ }
930
+
931
+ /* 运行态:输入台整体 busy 标记(aria-busy 等价,状态机语言) */
932
+ .ai-composer.is-running .ai-composer-box {
933
+ border-color: color-mix(in srgb, var(--token-accent) 30%, var(--token-line-soft));
934
+ }
935
+
936
+ /* 拖放文件高亮(粘贴/拖放附件入口,spec §11) */
937
+ .ai-composer-box.is-dragover {
938
+ border-color: var(--token-accent);
939
+ box-shadow: var(--icen-ring);
940
+ }
941
+
942
+ /* ══════════════ data-density 三档(summary 纯 CSS 降噪) ══════════════ */
943
+
944
+ /* summary:隐藏 meta、默认折叠 reasoning(head 展开仍可看)、次要消息压淡 */
945
+ .ai-chat[data-density='summary'] .ai-msg-meta {
946
+ display: none;
947
+ }
948
+ .ai-chat[data-density='summary'] .ai-reasoning-body {
949
+ display: none;
950
+ }
951
+ .ai-chat[data-density='summary'] .ai-reasoning:has(.ai-reasoning-head[aria-expanded='true']) .ai-reasoning-body {
952
+ display: block;
953
+ }
954
+ .ai-chat[data-density='summary'] .ai-msg--tool,
955
+ .ai-chat[data-density='summary'] .ai-msg--system {
956
+ opacity: 0.72;
957
+ }
958
+
959
+ /* verbose:当前与 normal 同视觉(透明度三档中两档生效;verbose 预留全显档位) */
960
+
961
+ /* ══════════════ keyframes(本文件局部) ══════════════ */
962
+
963
+ @keyframes ai-shimmer {
964
+ from {
965
+ background-position: 200% 0;
966
+ }
967
+ to {
968
+ background-position: -200% 0;
969
+ }
970
+ }
971
+
972
+ /* ══════════════ 动效守卫 ══════════════ */
973
+
974
+ @media (prefers-reduced-motion: reduce) {
975
+ .ai-msg,
976
+ .ai-chat-jump,
977
+ .ai-composer-popup {
978
+ animation: none;
979
+ }
980
+ .ai-chat-jump,
981
+ .ai-msg-actions,
982
+ .ai-composer-chip-remove,
983
+ .ai-composer-btn,
984
+ .ai-composer-send,
985
+ .ai-composer-model,
986
+ .ai-composer-model-chevron,
987
+ .ai-composer-popup-option,
988
+ .ai-composer-popup-search-input,
989
+ .ai-reasoning-head,
990
+ .ai-reasoning-head::before {
991
+ transition: none;
992
+ }
993
+ .ai-reasoning.is-streaming .ai-reasoning-head::after {
994
+ animation: none;
995
+ background: none;
996
+ -webkit-text-fill-color: currentColor;
997
+ color: var(--token-text-faint);
998
+ }
999
+ .ai-msg-body.is-streaming::after,
1000
+ .ai-reasoning-body.is-streaming::after {
1001
+ animation: none;
1002
+ }
1003
+ }
1004
+
1005
+ /* 触屏无 hover:消息操作常显(否则不可达) */
1006
+ @media (pointer: coarse) {
1007
+ .ai-msg-actions {
1008
+ opacity: 1;
1009
+ visibility: visible;
1010
+ transform: none;
1011
+ }
1012
+ }
1013
+
1014
+ /* 实时待办 chip(setComposerTodo:业界模式——plan 状态挂输入区,不进对话流) */
1015
+ .ai-composer-todo {
1016
+ display: inline-flex;
1017
+ align-items: center;
1018
+ gap: 5px;
1019
+ height: 28px;
1020
+ padding: 0 8px;
1021
+ border: 1px solid var(--token-line-soft);
1022
+ border-radius: var(--radius-full);
1023
+ background: var(--token-surface);
1024
+ color: var(--token-text-muted);
1025
+ font-family: var(--font-mono);
1026
+ font-size: var(--density-font-size-2xs);
1027
+ font-variant-numeric: tabular-nums;
1028
+ cursor: pointer;
1029
+ transition: color var(--icen-tx-fast), border-color var(--icen-tx-fast);
1030
+ }
1031
+ .ai-composer-todo svg { width: 13px; height: 13px; color: var(--token-accent); }
1032
+ .ai-composer-todo:hover { border-color: var(--icen-hover-border, var(--token-line)); color: var(--token-text); }
1033
+ .ai-composer-todo:focus-visible { outline: none; box-shadow: var(--icen-ring); }
1034
+ .ai-composer-todo.is-done svg { color: var(--token-success); }
1035
+ .ai-composer-todo.is-error svg { color: var(--token-warning); }
1036
+ .ai-composer-todo-pop { min-width: 240px; max-width: 320px; }
1037
+ .ai-composer-todo-pop .ai-todo { padding: 0; }