@veltra/ai 1.4.0 → 2.1.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 (87) hide show
  1. package/dist/chat/index.d.ts +3 -3
  2. package/dist/chat/transports/index.d.ts +2 -2
  3. package/dist/chat/transports/openai.d.ts +16 -12
  4. package/dist/chat/transports/openai.js +56 -15
  5. package/dist/chat/transports/openai.js.map +1 -1
  6. package/dist/chat/types.d.ts +41 -2
  7. package/dist/chat/use-chat.d.ts +26 -1
  8. package/dist/chat/use-chat.js +132 -13
  9. package/dist/chat/use-chat.js.map +1 -1
  10. package/dist/components/ai-chat/ai-chat.js +222 -41
  11. package/dist/components/ai-chat/ai-chat.js.map +1 -1
  12. package/dist/components/ai-chat/ask-question.js +2 -2
  13. package/dist/components/ai-chat/ask-question.js.map +1 -1
  14. package/dist/components/ai-chat/chat-input.js +81 -44
  15. package/dist/components/ai-chat/chat-input.js.map +1 -1
  16. package/dist/components/ai-chat/di.js.map +1 -1
  17. package/dist/components/ai-chat/message-item.js +101 -23
  18. package/dist/components/ai-chat/message-item.js.map +1 -1
  19. package/dist/components/ai-chat/message-list.js +217 -32
  20. package/dist/components/ai-chat/message-list.js.map +1 -1
  21. package/dist/components/ai-chat/model-picker.js +140 -0
  22. package/dist/components/ai-chat/model-picker.js.map +1 -0
  23. package/dist/components/ai-chat/queue-list.js +70 -0
  24. package/dist/components/ai-chat/queue-list.js.map +1 -0
  25. package/dist/components/ai-chat/side-panel.js +96 -0
  26. package/dist/components/ai-chat/side-panel.js.map +1 -0
  27. package/dist/components/ai-chat/style.d.ts +6 -0
  28. package/dist/components/ai-chat/style.js +6 -0
  29. package/dist/components/ai-chat/style2.css +552 -41
  30. package/dist/components/ai-chat/tool-call.js +61 -27
  31. package/dist/components/ai-chat/tool-call.js.map +1 -1
  32. package/dist/components/ai-orb/ai-orb.js +94 -0
  33. package/dist/components/ai-orb/ai-orb.js.map +1 -0
  34. package/dist/components/ai-orb/orb-renderer.js +411 -0
  35. package/dist/components/ai-orb/orb-renderer.js.map +1 -0
  36. package/dist/components/ai-orb/style.d.ts +1 -0
  37. package/dist/components/ai-orb/style.js +1 -0
  38. package/dist/components/ai-orb/style2.css +23 -0
  39. package/dist/index.d.ts +8 -6
  40. package/dist/index.js +2 -2
  41. package/dist/providers/types.d.ts +47 -0
  42. package/dist/style.js +1 -0
  43. package/dist/tools/ask-question/ask-question.d.ts +2 -17
  44. package/dist/tools/ask-question/index.d.ts +2 -2
  45. package/dist/tools/index.d.ts +3 -2
  46. package/dist/tools/index.js +10 -0
  47. package/dist/tools/index.js.map +1 -0
  48. package/dist/types/ai-chat.d.ts +35 -7
  49. package/dist/types/ai-orb.d.ts +27 -0
  50. package/dist/types/index.d.ts +2 -1
  51. package/package.json +8 -8
  52. package/src/chat/__test__/use-chat.test.ts +295 -9
  53. package/src/chat/transports/__test__/openai.test.ts +317 -0
  54. package/src/chat/transports/openai.ts +88 -26
  55. package/src/chat/types.ts +41 -1
  56. package/src/chat/use-chat.ts +177 -14
  57. package/src/components/ai-chat/__test__/ai-chat.test.ts +690 -12
  58. package/src/components/ai-chat/ai-chat.vue +241 -31
  59. package/src/components/ai-chat/chat-input.vue +78 -31
  60. package/src/components/ai-chat/di.ts +2 -0
  61. package/src/components/ai-chat/message-item.vue +98 -11
  62. package/src/components/ai-chat/message-list.vue +261 -21
  63. package/src/components/ai-chat/model-picker.vue +177 -0
  64. package/src/components/ai-chat/queue-list.vue +63 -0
  65. package/src/components/ai-chat/side-panel.vue +76 -0
  66. package/src/components/ai-chat/style.scss +621 -43
  67. package/src/components/ai-chat/style.ts +6 -0
  68. package/src/components/ai-chat/tool-call.vue +86 -42
  69. package/src/components/ai-orb/__test__/ai-orb.test.ts +251 -0
  70. package/src/components/ai-orb/ai-orb.vue +107 -0
  71. package/src/components/ai-orb/index.ts +1 -0
  72. package/src/components/ai-orb/orb-renderer.ts +505 -0
  73. package/src/components/ai-orb/style.scss +10 -0
  74. package/src/components/ai-orb/style.ts +1 -0
  75. package/src/components/index.ts +1 -0
  76. package/src/index.ts +8 -1
  77. package/src/providers/index.ts +1 -0
  78. package/src/providers/types.ts +46 -0
  79. package/src/style.ts +1 -0
  80. package/src/tools/ask-question/__test__/ask-question.test.ts +4 -4
  81. package/src/tools/index.ts +14 -1
  82. package/src/types/ai-chat.ts +35 -6
  83. package/src/types/ai-orb.ts +27 -0
  84. package/src/types/index.ts +1 -0
  85. package/dist/components/ai-chat/ai-chat.vue.d.ts +0 -36
  86. package/dist/components/ai-chat/index.d.ts +0 -2
  87. package/dist/components/index.d.ts +0 -3
@@ -5,26 +5,314 @@
5
5
  $root-name: ai-chat;
6
6
 
7
7
  @include m.b($root-name) {
8
- display: flex;
9
- flex-direction: column;
10
8
  height: 100%;
11
9
  min-height: 0;
12
10
  font-size: fn.use-var(font-size-main, default);
13
11
  color: fn.use-var(text-color, main);
12
+ // 底层背景与卡片(输入区 / 侧面板等 bg-color-top 元素)形成柔和对比,替代分割线
13
+ background-color: fn.use-var(bg-color, bottom);
14
14
 
15
- @include m.e(list) {
16
- flex: 1;
15
+ // 会话主列(消息列表 + 队列 + 输入区):内容限宽居中,面板开合时排版不形变
16
+ @include m.e(main) {
17
+ min-width: 0;
17
18
  min-height: 0;
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ padding: 0 fn.use-var(gap, default);
23
+
24
+ // 空会话:列表区与末尾占位各分一半剩余空间,输入区垂直居中,
25
+ // 欢迎区(orb + 快捷提问)贴于输入区上方并与之左对齐;有消息后自动回落到底部
26
+ @include m.is(empty) {
27
+ &::after {
28
+ content: '';
29
+ flex: 1;
30
+ }
31
+ }
18
32
  }
19
33
 
20
- @include m.e(welcome) {
34
+ // 拖拽中:固定光标,并禁用面板内容指针事件(避免 iframe 等内容吞掉 mousemove)
35
+ @include m.is(resizing) {
36
+ cursor: col-resize;
37
+
38
+ .u-#{$root-name}__panel-body {
39
+ pointer-events: none;
40
+ }
41
+ }
42
+
43
+ // 右侧侧边面板(renderTo: 'panel' 的工具渲染区),宽度由 ULayout 列轨控制。
44
+ // 悬浮卡片形态:面板本体透明,以内边距让出与会话区的间距(代替生硬的左分割线),
45
+ // 圆角卡片仅靠底层背景对比与阴影区分;面板元素始终保持整轨宽度,避免宽度观测回环
46
+ @include m.e(panel) {
47
+ min-width: 0;
48
+ padding: fn.use-var(gap, small);
49
+ padding-left: calc(fn.use-var(gap, small) / 2);
50
+ box-sizing: border-box;
51
+ animation: u-ai-chat-panel-in 0.2s ease-out;
52
+
53
+ @media (prefers-reduced-motion: reduce) {
54
+ animation: none;
55
+ }
56
+ }
57
+
58
+ @include m.e(panel-card) {
59
+ display: flex;
60
+ flex-direction: column;
21
61
  height: 100%;
62
+ min-height: 0;
63
+ overflow: hidden;
64
+ border-radius: fn.use-var(radius, large);
65
+ background-color: fn.use-var(bg-color, top);
66
+ box-shadow: fn.use-var(shadow, sm);
67
+ }
68
+
69
+ @keyframes u-ai-chat-panel-in {
70
+ from {
71
+ transform: translateX(16px);
72
+ opacity: 0;
73
+ }
74
+
75
+ to {
76
+ transform: translateX(0);
77
+ opacity: 1;
78
+ }
79
+ }
80
+
81
+ // 面板头部:无下分割线,靠留白与卡片边界区分内容区
82
+ @include m.e(panel-header) {
83
+ flex: none;
84
+ @include m.flex(flex, flex-start, center);
85
+ gap: fn.use-var(gap, small);
86
+ padding: fn.use-var(gap, default);
87
+ padding-bottom: fn.use-var(gap, small);
88
+ }
89
+
90
+ // 头部图标做成圆角底托,状态色只作用于图标本身
91
+ @include m.e(panel-icon) {
92
+ flex-shrink: 0;
93
+ width: 26px;
94
+ height: 26px;
95
+ border-radius: fn.use-var(radius, small);
96
+ background-color: fn.use-var(bg-color, middle);
97
+ font-size: 14px;
22
98
  @include m.flex(flex, center, center);
99
+
100
+ @include m.is(active) {
101
+ color: fn.use-var(color, primary);
102
+ }
103
+
104
+ @include m.is(warning) {
105
+ color: fn.use-var(color, warning);
106
+ }
107
+
108
+ @include m.is(success) {
109
+ color: fn.use-var(color, success);
110
+ }
111
+
112
+ @include m.is(danger) {
113
+ color: fn.use-var(color, danger);
114
+ }
115
+ }
116
+
117
+ @include m.e(panel-title) {
118
+ flex: 1;
119
+ min-width: 0;
120
+ @include m.ellipsis;
121
+ font-weight: 600;
122
+ color: fn.use-var(text-color, title);
123
+ }
124
+
125
+ @include m.e(panel-close) {
126
+ flex-shrink: 0;
127
+ width: 22px;
128
+ height: 22px;
129
+ border-radius: 50%;
130
+ cursor: pointer;
23
131
  color: fn.use-var(text-color, assist);
132
+ transition:
133
+ color 0.15s ease,
134
+ background-color 0.15s ease;
135
+ @include m.flex(flex, center, center);
136
+
137
+ &:hover {
138
+ color: fn.use-var(text-color, main);
139
+ background-color: fn.use-var(bg-color, hover);
140
+ }
141
+ }
142
+
143
+ @include m.e(panel-body) {
144
+ flex: 1;
145
+ min-height: 0;
146
+ }
147
+
148
+ @include m.e(panel-fallback) {
149
+ margin: 0;
24
150
  padding: fn.use-var(gap, default);
151
+ font-family: fn.use-var(font-family, code);
152
+ font-size: fn.use-var(font-size-main, small);
153
+ line-height: 1.5;
154
+ white-space: pre-wrap;
155
+ word-break: break-all;
156
+ }
157
+
158
+ // 面板工具卡片内的「查看面板」入口
159
+ @include m.e(tool-call-panel-entry) {
160
+ @include m.flex(flex, flex-start, center);
161
+ gap: fn.use-var(gap, small);
162
+ }
163
+
164
+ @include m.e(tool-call-panel-hint) {
165
+ color: fn.use-var(text-color, assist);
166
+ }
167
+
168
+ @include m.e(list-wrap) {
169
+ position: relative;
170
+ flex: 1;
171
+ min-height: 0;
172
+ width: 100%;
173
+ max-width: 800px;
174
+ display: flex;
175
+ flex-direction: column;
176
+ }
177
+
178
+ @include m.e(list) {
179
+ flex: 1;
180
+ min-height: 0;
181
+
182
+ // 内容不足一屏时撑满滚动视口并按列排布,空会话欢迎区才能占满整列做底部对齐
183
+ .u-scroll__content {
184
+ min-height: 100%;
185
+ display: flex;
186
+ flex-direction: column;
187
+ }
188
+ }
189
+
190
+ // 欢迎区仅在空会话渲染:占满整列并底部对齐,使其贴在居中的输入区上方
191
+ @include m.e(welcome) {
192
+ flex: 1;
193
+ @include m.flex(flex, center, flex-end);
194
+ color: fn.use-var(text-color, assist);
195
+ padding: fn.use-var(gap, default) 0;
25
196
  text-align: center;
26
197
  }
27
198
 
199
+ @include m.e(welcome-inner) {
200
+ // 固定锚点:球钉在左侧不动(与输入框左对齐),文本气泡只向右延展
201
+ //(轮换 / 不同长度文案都不会让球瞬移)
202
+ @include m.flex(flex, flex-start, center);
203
+ gap: fn.use-var(gap, default);
204
+ width: 100%;
205
+ }
206
+
207
+ @include m.e(welcome-item-wrap) {
208
+ flex: 1;
209
+ min-width: 0;
210
+ display: flex;
211
+ align-items: center;
212
+ // 预留两行高度:长文案换行时行高不抖动
213
+ min-height: 54px;
214
+ }
215
+
216
+ @include m.e(welcome-item) {
217
+ max-width: 100%;
218
+ padding: fn.use-var(gap, small) fn.use-var(gap, default);
219
+ border: none;
220
+ border-radius: fn.use-var(radius, large);
221
+ background-color: fn.use-var(bg-color, top);
222
+ color: fn.use-var(text-color, second);
223
+ font-size: 13px;
224
+ text-align: left;
225
+ cursor: pointer;
226
+ transition:
227
+ color 0.15s ease,
228
+ background-color 0.15s ease;
229
+
230
+ &:hover {
231
+ color: fn.use-var(color, primary);
232
+ background-color: fn.use-var(color, primary, 'light-9');
233
+ }
234
+ }
235
+
236
+ // 欢迎语逐条轮换的切换过渡
237
+ @include m.em(welcome, 'swap-enter-active') {
238
+ transition:
239
+ opacity 0.18s ease,
240
+ transform 0.18s ease;
241
+ }
242
+
243
+ @include m.em(welcome, 'swap-leave-active') {
244
+ transition:
245
+ opacity 0.12s ease,
246
+ transform 0.12s ease;
247
+ }
248
+
249
+ @include m.em(welcome, 'swap-enter-from') {
250
+ opacity: 0;
251
+ transform: translateY(6px);
252
+ }
253
+
254
+ @include m.em(welcome, 'swap-leave-to') {
255
+ opacity: 0;
256
+ transform: translateY(-6px);
257
+ }
258
+
259
+ // 生成中的统一工作状态指示(列表末尾)
260
+ @include m.e(working) {
261
+ @include m.flex(flex, flex-start, center);
262
+ gap: fn.use-var(gap, small);
263
+ padding: 0 fn.use-var(gap, default) fn.use-var(gap, small);
264
+ }
265
+
266
+ @include m.e(working-text) {
267
+ font-size: fn.use-var(font-size-main, small);
268
+ }
269
+
270
+ // 「最新消息」悬浮按钮:上翻浏览时出现,点击回到底部
271
+ @include m.e(to-latest) {
272
+ position: absolute;
273
+ top: fn.use-var(gap, small);
274
+ left: 50%;
275
+ transform: translateX(-50%);
276
+ z-index: 1;
277
+ @include m.flex(flex, center, center);
278
+ gap: 4px;
279
+ padding: 4px 12px;
280
+ border: none;
281
+ border-radius: 999px;
282
+ background-color: fn.use-var(bg-color, top);
283
+ color: fn.use-var(text-color, second);
284
+ font-size: fn.use-var(font-size-main, small);
285
+ box-shadow: 0 2px 8px fn.color-a(shadow, 20, color);
286
+ cursor: pointer;
287
+ transition: color 0.15s ease;
288
+
289
+ &:hover {
290
+ color: fn.use-var(color, primary);
291
+ }
292
+ }
293
+
294
+ @include m.em(to-latest, 'fade-enter-active') {
295
+ transition:
296
+ opacity 0.15s ease,
297
+ transform 0.15s ease;
298
+ }
299
+
300
+ @include m.em(to-latest, 'fade-leave-active') {
301
+ transition:
302
+ opacity 0.1s ease,
303
+ transform 0.1s ease;
304
+ }
305
+
306
+ @include m.em(to-latest, 'fade-enter-from') {
307
+ opacity: 0;
308
+ transform: translateX(-50%) translateY(-4px);
309
+ }
310
+
311
+ @include m.em(to-latest, 'fade-leave-to') {
312
+ opacity: 0;
313
+ transform: translateX(-50%) translateY(-4px);
314
+ }
315
+
28
316
  @include m.e(message) {
29
317
  padding: fn.use-var(gap, small) fn.use-var(gap, default);
30
318
 
@@ -63,7 +351,33 @@ $root-name: ai-chat;
63
351
  }
64
352
 
65
353
  @include m.e(message-content) {
66
- line-height: 1.7;
354
+ font-size: 13px;
355
+ line-height: 1.6;
356
+ // 压缩 markstream 流式排版:正文字号 / 行高 / 标题档位 / 块级间距(变量见 markstream-vue 样式表)
357
+ --ms-text-body: 13px;
358
+ --ms-leading-body: 1.6;
359
+ --ms-text-h1: 1.55em;
360
+ --ms-text-h2: 1.35em;
361
+ --ms-text-h3: 1.15em;
362
+ --ms-text-h4: 1em;
363
+ --ms-text-h5: 1em;
364
+ --ms-text-h6: 1em;
365
+ --ms-flow-heading-1-mt: 0.9em;
366
+ --ms-flow-heading-1-mb: 0.35em;
367
+ --ms-flow-heading-2-mt: 0.8em;
368
+ --ms-flow-heading-2-mb: 0.3em;
369
+ --ms-flow-heading-3-mt: 0.7em;
370
+ --ms-flow-heading-3-mb: 0.25em;
371
+ --ms-flow-paragraph-y: 0.55em;
372
+ --ms-flow-list-y: 0.45em;
373
+ --ms-flow-list-item-y: 0.08em;
374
+ --ms-flow-list-indent: 1.3em;
375
+ --ms-flow-codeblock-y: 0.7em;
376
+ --ms-flow-blockquote-y: 0.55em;
377
+ --ms-flow-table-y: 0.7em;
378
+ --ms-flow-hr-y: 0.9em;
379
+ --ms-flow-admonition-y: 0.7em;
380
+ --ms-flow-diagram-y: 0.7em;
67
381
  }
68
382
 
69
383
  @include m.e(message-status) {
@@ -73,61 +387,92 @@ $root-name: ai-chat;
73
387
  }
74
388
 
75
389
  @include m.e(message-actions) {
76
- margin-top: fn.use-var(gap, small);
77
- color: fn.use-var(text-color, assist);
78
- cursor: pointer;
79
- display: inline-flex;
80
-
81
- &:hover {
82
- color: fn.use-var(color, primary);
83
- }
390
+ display: flex;
391
+ flex-wrap: wrap;
392
+ gap: fn.use-var(gap, small);
84
393
  }
85
394
 
86
395
  // 思考过程
87
396
  @include m.e(reasoning) {
88
- margin-bottom: fn.use-var(gap, small);
397
+ margin-bottom: 4px;
89
398
  color: fn.use-var(text-color, assist);
90
399
 
91
400
  @include m.is(expanded) {
92
401
  @include m.bem($root-name, 'reasoning-chevron') {
93
- transform: rotate(180deg);
402
+ transform: rotate(90deg);
94
403
  }
95
404
  }
96
405
  }
97
406
 
98
407
  @include m.e(reasoning-header) {
99
408
  @include m.flex(flex, flex-start, center);
100
- gap: fn.use-var(gap, small);
409
+ gap: 4px;
101
410
  cursor: pointer;
102
411
  user-select: none;
103
412
  font-size: fn.use-var(font-size-main, small);
104
413
  }
105
414
 
415
+ @include m.e(reasoning-title) {
416
+ line-height: 1.5;
417
+ }
418
+
106
419
  @include m.e(reasoning-chevron) {
420
+ font-size: 12px;
107
421
  transition: transform 0.2s;
108
422
  }
109
423
 
110
424
  @include m.e(reasoning-content) {
111
- margin-top: fn.use-var(gap, small);
112
- padding-left: fn.use-var(gap, default);
425
+ margin-top: 4px;
426
+ padding-left: fn.use-var(gap, small);
113
427
  border-left: fn.use-var(border-muted);
428
+ }
429
+
430
+ @include m.e(reasoning-text) {
431
+ padding-right: 4px;
114
432
  white-space: pre-wrap;
115
433
  word-break: break-word;
116
434
  font-size: fn.use-var(font-size-main, small);
117
- line-height: 1.6;
435
+ line-height: 1.55;
118
436
  }
119
437
 
120
- // 工具调用卡片
438
+ // 工具调用卡片(复用 UCollapseItem:头部经 #header 插槽自定义,body 为折叠内容区)
121
439
  @include m.e(tool-call) {
122
- margin: fn.use-var(gap, small) 0;
440
+ margin: 4px 0;
441
+ background-color: fn.use-var(bg-color, top);
442
+ font-size: fn.use-var(font-size-main, small);
443
+
444
+ // 相邻工具卡片保持紧凑间距(覆盖 UCollapseItem 的兄弟间距)
445
+ & + #{fn.bem($root-name, tool-call)} {
446
+ margin-top: 4px;
447
+ }
448
+
449
+ // 覆盖 UCollapseItem 默认头部/内容区节奏,保持紧凑单行头
450
+ .u-collapse__header {
451
+ gap: 6px;
452
+ padding: 5px fn.use-var(gap, small);
453
+ font-weight: 400;
454
+ letter-spacing: normal;
455
+ }
456
+
457
+ .u-collapse__content {
458
+ border-top: fn.use-var(border-muted);
459
+ padding: fn.use-var(gap, small);
460
+ line-height: 1.5;
461
+ }
462
+ }
463
+
464
+ @include m.e(tool-call-chevron) {
465
+ flex-shrink: 0;
466
+ font-size: 12px;
467
+ color: fn.use-var(text-color, assist);
123
468
  }
124
469
 
125
470
  @include m.e(tool-call-status) {
126
471
  flex-shrink: 0;
472
+ font-size: 13px;
127
473
 
128
- @include m.is(loading) {
474
+ @include m.is(active) {
129
475
  color: fn.use-var(color, primary);
130
- animation: u-ai-chat-rotate 1s linear infinite;
131
476
  }
132
477
 
133
478
  @include m.is(warning) {
@@ -144,8 +489,7 @@ $root-name: ai-chat;
144
489
  }
145
490
 
146
491
  @include m.e(tool-call-name) {
147
- font-family: fn.use-var(font-family, code);
148
- font-size: fn.use-var(font-size-main, small);
492
+ font-weight: 500;
149
493
  color: fn.use-var(text-color, title);
150
494
  flex-shrink: 0;
151
495
  }
@@ -155,31 +499,34 @@ $root-name: ai-chat;
155
499
  min-width: 0;
156
500
  @include m.ellipsis;
157
501
  color: fn.use-var(text-color, assist);
158
- font-size: fn.use-var(font-size-main, small);
159
502
  }
160
503
 
161
504
  @include m.e(tool-call-confirm) {
162
505
  flex-shrink: 0;
163
506
  @include m.flex(flex, flex-start, center);
164
- gap: fn.use-var(gap, small);
507
+ gap: 4px;
165
508
  }
166
509
 
167
510
  @include m.e(tool-call-section) {
168
- font-size: fn.use-var(font-size-main, small);
169
511
  color: fn.use-var(text-color, assist);
170
- margin: fn.use-var(gap, small) 0;
512
+ margin-bottom: 4px;
513
+
514
+ &:not(:first-child) {
515
+ margin-top: fn.use-var(gap, small);
516
+ }
171
517
  }
172
518
 
173
519
  @include m.e(tool-call-code) {
174
520
  margin: 0;
175
- padding: fn.use-var(gap, small);
176
- border-radius: fn.use-var(radius, default);
521
+ padding: 4px fn.use-var(gap, small);
522
+ border-radius: fn.use-var(radius, small);
177
523
  background-color: fn.use-var(bg-color, bottom);
178
524
  font-family: fn.use-var(font-family, code);
179
525
  font-size: fn.use-var(font-size-main, small);
526
+ line-height: 1.5;
180
527
  white-space: pre-wrap;
181
528
  word-break: break-all;
182
- max-height: 240px;
529
+ max-height: 200px;
183
530
  overflow: auto;
184
531
  }
185
532
 
@@ -339,18 +686,88 @@ $root-name: ai-chat;
339
686
  color: fn.use-var(text-color, assist);
340
687
  }
341
688
 
342
- // 输入区
689
+ // 待发送队列(生成中提交的消息在此排队)
690
+ @include m.e(queue) {
691
+ flex-shrink: 0;
692
+ width: 100%;
693
+ max-width: 800px;
694
+ box-sizing: border-box;
695
+ margin-bottom: 4px;
696
+ border-radius: fn.use-var(radius, default);
697
+ background-color: fn.use-var(bg-color, middle);
698
+ padding: 4px;
699
+ font-size: fn.use-var(font-size-main, small);
700
+ }
701
+
702
+ @include m.e(queue-head) {
703
+ padding: 2px 6px;
704
+ color: fn.use-var(text-color, assist);
705
+ }
706
+
707
+ @include m.e(queue-item) {
708
+ @include m.flex(flex, flex-start, center);
709
+ gap: 6px;
710
+ padding: 3px 6px;
711
+ border-radius: fn.use-var(radius, small);
712
+
713
+ &:hover {
714
+ background-color: fn.use-var(bg-color, hover);
715
+ }
716
+ }
717
+
718
+ @include m.e(queue-text) {
719
+ flex: 1;
720
+ min-width: 0;
721
+ @include m.ellipsis;
722
+ color: fn.use-var(text-color, main);
723
+ }
724
+
725
+ @include m.e(queue-actions) {
726
+ flex-shrink: 0;
727
+ @include m.flex(flex, flex-start, center);
728
+ gap: 2px;
729
+ }
730
+
731
+ @include m.e(queue-action) {
732
+ @include m.flex(flex, flex-start, center);
733
+ gap: 2px;
734
+ border: none;
735
+ background: none;
736
+ padding: 1px 4px;
737
+ border-radius: fn.use-var(radius, small);
738
+ color: fn.use-var(text-color, assist);
739
+ font-size: inherit;
740
+ font-family: inherit;
741
+ white-space: nowrap;
742
+ cursor: pointer;
743
+ transition:
744
+ color 0.15s ease,
745
+ background-color 0.15s ease;
746
+
747
+ &:hover {
748
+ color: fn.use-var(color, primary);
749
+ background-color: fn.use-var(color, primary, 'light-9');
750
+ }
751
+
752
+ @include m.m(primary) {
753
+ color: fn.use-var(color, primary);
754
+ }
755
+ }
756
+
757
+ // 输入区:无边框卡片,聚焦时用柔和的主色描边提示
343
758
  @include m.e(input-area) {
344
759
  flex-shrink: 0;
345
- margin: fn.use-var(gap, default);
346
- margin-top: fn.use-var(gap, small);
347
- border: fn.use-var(border-muted);
760
+ width: 100%;
761
+ max-width: 800px;
762
+ box-sizing: border-box;
763
+ margin: fn.use-var(gap, small) 0 fn.use-var(gap, default);
348
764
  border-radius: fn.use-var(radius, large);
349
765
  background-color: fn.use-var(bg-color, top);
350
766
  padding: fn.use-var(gap, small);
767
+ transition: box-shadow 0.15s ease;
351
768
 
352
769
  &:focus-within {
353
- border-color: fn.use-var(color, primary);
770
+ box-shadow: 0 0 0 1px fn.use-var(color, primary);
354
771
  }
355
772
  }
356
773
 
@@ -374,13 +791,61 @@ $root-name: ai-chat;
374
791
 
375
792
  @include m.e(input-toolbar) {
376
793
  @include m.flex(flex, space-between, center);
794
+ gap: fn.use-var(gap, small);
377
795
  padding: 0 fn.use-var(gap, small);
378
796
  }
379
797
 
798
+ @include m.e(input-toolbar-left) {
799
+ @include m.flex(flex, flex-start, center);
800
+ gap: fn.use-var(gap, small);
801
+ flex-shrink: 0;
802
+ }
803
+
804
+ @include m.e(input-toolbar-right) {
805
+ @include m.flex(flex, flex-end, center);
806
+ gap: fn.use-var(gap, small);
807
+ min-width: 0;
808
+ }
809
+
810
+ @include m.e(model-trigger) {
811
+ @include m.flex(flex, flex-start, center);
812
+ gap: 4px;
813
+ max-width: 220px;
814
+ padding: 2px fn.use-var(gap, small);
815
+ border: none;
816
+ border-radius: fn.use-var(radius, default);
817
+ background: transparent;
818
+ color: fn.use-var(text-color, main);
819
+ font-family: inherit;
820
+ font-size: inherit;
821
+ cursor: pointer;
822
+ transition: background-color 0.15s ease;
823
+
824
+ &:hover {
825
+ background-color: var(--u-nav-hover-bg);
826
+ }
827
+ }
828
+
829
+ @include m.e(model-trigger-label) {
830
+ @include m.ellipsis;
831
+ }
832
+
833
+ @include m.e(model-trigger-reasoning) {
834
+ flex-shrink: 0;
835
+ color: fn.use-var(text-color, assist);
836
+ }
837
+
838
+ @include m.e(model-trigger-arrow) {
839
+ flex-shrink: 0;
840
+ font-size: 12px;
841
+ color: fn.use-var(text-color, assist);
842
+ }
843
+
380
844
  @include m.e(input-attach) {
381
845
  color: fn.use-var(text-color, assist);
382
846
  cursor: pointer;
383
847
  font-size: 18px;
848
+ flex-shrink: 0;
384
849
 
385
850
  &:hover {
386
851
  color: fn.use-var(color, primary);
@@ -429,11 +894,124 @@ $root-name: ai-chat;
429
894
  }
430
895
  }
431
896
 
432
- @keyframes u-ai-chat-rotate {
433
- from {
434
- transform: rotate(0deg);
897
+ // 模型选择面板经 teleport 挂载到 popper 容器,不处于 .u-ai-chat 内,需独立定义
898
+ .u-#{$root-name}__model-panel {
899
+ padding: 4px;
900
+ user-select: none;
901
+
902
+ .u-#{$root-name}__model-panel-list {
903
+ min-width: 0;
904
+ }
905
+
906
+ .u-#{$root-name}__model-option-main {
907
+ padding: 6px 8px;
908
+ border-radius: calc(fn.use-var(radius, default) + 1px);
909
+ cursor: pointer;
910
+ transition: background-color 0.15s ease;
911
+
912
+ &:hover {
913
+ background-color: var(--u-nav-hover-bg);
914
+ }
915
+ }
916
+
917
+ .u-#{$root-name}__model-option-head {
918
+ @include m.flex(flex, space-between, center);
919
+ gap: 12px;
920
+ color: fn.use-var(text-color, main);
921
+ }
922
+
923
+ .u-#{$root-name}__model-option-name {
924
+ @include m.ellipsis;
925
+ }
926
+
927
+ .u-#{$root-name}__model-option-actions {
928
+ @include m.flex(flex, flex-start, center);
929
+ flex-shrink: 0;
930
+ gap: 6px;
435
931
  }
436
- to {
437
- transform: rotate(360deg);
932
+
933
+ .u-#{$root-name}__model-option-check {
934
+ flex-shrink: 0;
935
+ font-size: 14px;
936
+ color: fn.use-var(color, primary);
937
+
938
+ @include m.is(hidden) {
939
+ visibility: hidden;
940
+ }
941
+ }
942
+
943
+ .u-#{$root-name}__model-option-reasoning {
944
+ @include m.flex(flex, flex-start, center);
945
+ gap: 2px;
946
+ flex-shrink: 0;
947
+ padding: 1px 6px;
948
+ border: none;
949
+ border-radius: fn.use-var(radius, small);
950
+ background: fn.use-var(color, primary, 'light-9');
951
+ color: fn.use-var(color, primary);
952
+ font-size: fn.use-var(font-size-main, small);
953
+ cursor: pointer;
954
+ transition:
955
+ background-color 0.15s ease,
956
+ color 0.15s ease;
957
+
958
+ &:hover {
959
+ background: fn.use-var(color, primary, 'light-7');
960
+ }
961
+ }
962
+
963
+ .u-#{$root-name}__model-option-reasoning-arrow {
964
+ font-size: 12px;
965
+ transition: transform 0.2s ease;
966
+ }
967
+
968
+ .u-#{$root-name}__model-option-reasoning.is-expanded {
969
+ .u-#{$root-name}__model-option-reasoning-arrow {
970
+ transform: rotate(180deg);
971
+ }
972
+ }
973
+
974
+ .u-#{$root-name}__model-option-desc {
975
+ margin-top: 2px;
976
+ font-size: fn.use-var(font-size-main, small);
977
+ color: fn.use-var(text-color, assist);
978
+ }
979
+
980
+ // 内联思考强度:grid 行高 0fr → 1fr 过渡,展开/收起平滑无跳变
981
+ .u-#{$root-name}__reasoning-levels {
982
+ display: grid;
983
+ grid-template-rows: 0fr;
984
+ transition: grid-template-rows 0.2s cubic-bezier(0.4, 0, 0.2, 1);
985
+
986
+ @include m.is(expanded) {
987
+ grid-template-rows: 1fr;
988
+ }
989
+ }
990
+
991
+ .u-#{$root-name}__reasoning-levels-inner {
992
+ min-height: 0;
993
+ overflow: hidden;
994
+ }
995
+
996
+ .u-#{$root-name}__reasoning-option {
997
+ @include m.flex(flex, space-between, center);
998
+ gap: 12px;
999
+ margin-left: 8px;
1000
+ padding: 5px 8px;
1001
+ border-radius: calc(fn.use-var(radius, default) + 1px);
1002
+ font-size: fn.use-var(font-size-main, small);
1003
+ color: fn.use-var(text-color, main);
1004
+ cursor: pointer;
1005
+ transition:
1006
+ background-color 0.15s ease,
1007
+ color 0.15s ease;
1008
+
1009
+ &:hover {
1010
+ background-color: var(--u-nav-hover-bg);
1011
+ }
1012
+
1013
+ @include m.is(active) {
1014
+ color: fn.use-var(color, primary);
1015
+ }
438
1016
  }
439
1017
  }